mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-05-31 04:05:50 -04:00
Merge remote-tracking branch 'upstream/master' into random
This commit is contained in:
commit
a2c35e6dba
@ -200,8 +200,8 @@ jobs:
|
|||||||
persistCredentials: true
|
persistCredentials: true
|
||||||
|
|
||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
displayName: "Check out web"
|
displayName: "Check out web (master, release or tag)"
|
||||||
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'))
|
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:
|
inputs:
|
||||||
script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
|
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:
|
inputs:
|
||||||
targetType: 'filePath' # Optional. Options: filePath, inline
|
targetType: 'filePath' # Optional. Options: filePath, inline
|
||||||
filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath
|
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
|
#script: '# Write your PowerShell commands here.Write-Host Hello World' # Required when targetType == Inline
|
||||||
errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue
|
errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue
|
||||||
#failOnStderr: false # Optional
|
#failOnStderr: false # Optional
|
||||||
|
@ -1,8 +1,59 @@
|
|||||||
srpm:
|
VERSION := $(shell sed -ne '/^Version:/s/.* *//p' \
|
||||||
dnf -y install git
|
deployment/fedora-package-x64/pkg-src/jellyfin.spec)
|
||||||
git submodule update --init --recursive
|
|
||||||
cd deployment/fedora-package-x64; \
|
deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz:
|
||||||
./create_tarball.sh; \
|
curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
|
||||||
rpmbuild -bs pkg-src/jellyfin.spec \
|
https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \
|
||||||
--define "_sourcedir $$PWD/pkg-src/" \
|
|| curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
|
||||||
--define "_srcrpmdir $(outdir)"
|
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)"
|
||||||
|
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -30,6 +30,7 @@ assignees: ''
|
|||||||
- OS: [e.g. Docker, Debian, Windows]
|
- OS: [e.g. Docker, Debian, Windows]
|
||||||
- Browser: [e.g. Firefox, Chrome, Safari]
|
- Browser: [e.g. Firefox, Chrome, Safari]
|
||||||
- Jellyfin Version: [e.g. 10.0.1]
|
- Jellyfin Version: [e.g. 10.0.1]
|
||||||
|
- Installed Plugins: [e.g. none, Fanart, Anime, etc.]
|
||||||
- Reverse proxy: [e.g. no, nginx, apache, etc.]
|
- Reverse proxy: [e.g. no, nginx, apache, etc.]
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
|
13
.github/stale.yml
vendored
13
.github/stale.yml
vendored
@ -1,7 +1,7 @@
|
|||||||
# Number of days of inactivity before an issue becomes stale
|
# 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
|
# Number of days of inactivity before a stale issue is closed
|
||||||
daysUntilClose: 14
|
daysUntilClose: 21
|
||||||
# Issues with these labels will never be considered stale
|
# Issues with these labels will never be considered stale
|
||||||
exemptLabels:
|
exemptLabels:
|
||||||
- regression
|
- regression
|
||||||
@ -11,12 +11,15 @@ exemptLabels:
|
|||||||
- future
|
- future
|
||||||
- feature
|
- feature
|
||||||
- enhancement
|
- enhancement
|
||||||
|
- confirmed
|
||||||
# Label to use when marking an issue as stale
|
# Label to use when marking an issue as stale
|
||||||
staleLabel: stale
|
staleLabel: stale
|
||||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
markComment: >
|
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.
|
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 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).
|
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
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
closeComment: false
|
closeComment: false
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -268,3 +268,6 @@ doc/
|
|||||||
# Deployment artifacts
|
# Deployment artifacts
|
||||||
dist
|
dist
|
||||||
*.exe
|
*.exe
|
||||||
|
|
||||||
|
# BenchmarkDotNet artifacts
|
||||||
|
BenchmarkDotNet.Artifacts
|
||||||
|
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@ -10,7 +10,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"preLaunchTask": "build",
|
"preLaunchTask": "build",
|
||||||
// If you have changed target frameworks, make sure to update the program path.
|
// 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": [],
|
"args": [],
|
||||||
"cwd": "${workspaceFolder}/Jellyfin.Server",
|
"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
|
// 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}"
|
"processId": "${command:pickProcess}"
|
||||||
}
|
}
|
||||||
,]
|
,]
|
||||||
}
|
}
|
||||||
|
28
Dockerfile
28
Dockerfile
@ -1,8 +1,8 @@
|
|||||||
ARG DOTNET_VERSION=2.2
|
ARG DOTNET_VERSION=3.0
|
||||||
ARG FFMPEG_VERSION=latest
|
ARG FFMPEG_VERSION=latest
|
||||||
|
|
||||||
FROM node:alpine as web-builder
|
FROM node:alpine as web-builder
|
||||||
ARG JELLYFIN_WEB_VERSION=v10.5.0
|
ARG JELLYFIN_WEB_VERSION=master
|
||||||
RUN apk add curl \
|
RUN apk add curl \
|
||||||
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||||
&& cd jellyfin-web-* \
|
&& cd jellyfin-web-* \
|
||||||
@ -10,33 +10,39 @@ RUN apk add curl \
|
|||||||
&& yarn build \
|
&& yarn build \
|
||||||
&& mv dist /dist
|
&& 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
|
WORKDIR /repo
|
||||||
COPY . .
|
COPY . .
|
||||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
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"
|
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 jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
|
||||||
|
FROM debian:buster-slim
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}
|
COPY --from=ffmpeg /opt/ffmpeg /opt/ffmpeg
|
||||||
COPY --from=ffmpeg / /
|
|
||||||
COPY --from=builder /jellyfin /jellyfin
|
COPY --from=builder /jellyfin /jellyfin
|
||||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||||
# Install dependencies:
|
# Install dependencies:
|
||||||
# libfontconfig1: needed for Skia
|
# libfontconfig1: needed for Skia
|
||||||
|
# libgomp1: needed for ffmpeg
|
||||||
|
# libva-drm2: needed for ffmpeg
|
||||||
# mesa-va-drivers: needed for VAAPI
|
# mesa-va-drivers: needed for VAAPI
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
&& 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 clean autoclean \
|
||||||
&& apt-get autoremove \
|
&& apt-get autoremove \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& mkdir -p /cache /config /media \
|
&& 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
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
VOLUME /cache /config /media
|
||||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||||
--datadir /config \
|
"--datadir", "/config", \
|
||||||
--cachedir /cache \
|
"--cachedir", "/cache", \
|
||||||
--ffmpeg /usr/local/bin/ffmpeg
|
"--ffmpeg", "/usr/local/bin/ffmpeg"]
|
||||||
|
@ -4,7 +4,7 @@ ARG DOTNET_VERSION=3.0
|
|||||||
|
|
||||||
|
|
||||||
FROM node:alpine as web-builder
|
FROM node:alpine as web-builder
|
||||||
ARG JELLYFIN_WEB_VERSION=v10.5.0
|
ARG JELLYFIN_WEB_VERSION=master
|
||||||
RUN apk add curl \
|
RUN apk add curl \
|
||||||
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||||
&& cd jellyfin-web-* \
|
&& cd jellyfin-web-* \
|
||||||
@ -17,8 +17,6 @@ FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
|||||||
WORKDIR /repo
|
WORKDIR /repo
|
||||||
COPY . .
|
COPY . .
|
||||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
# TODO Remove or update the sed line when we update dotnet version.
|
|
||||||
RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
|
|
||||||
# Discard objs - may cause failures if exists
|
# Discard objs - may cause failures if exists
|
||||||
RUN find . -type d -name obj | xargs -r rm -r
|
RUN find . -type d -name obj | xargs -r rm -r
|
||||||
# Build
|
# 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 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
|
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
&& 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=builder /jellyfin /jellyfin
|
||||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||||
|
|
||||||
|
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||||
|
|
||||||
EXPOSE 8096
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
VOLUME /cache /config /media
|
||||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||||
--datadir /config \
|
"--datadir", "/config", \
|
||||||
--cachedir /cache \
|
"--cachedir", "/cache", \
|
||||||
--ffmpeg /usr/bin/ffmpeg
|
"--ffmpeg", "/usr/bin/ffmpeg"]
|
||||||
|
@ -4,7 +4,7 @@ ARG DOTNET_VERSION=3.0
|
|||||||
|
|
||||||
|
|
||||||
FROM node:alpine as web-builder
|
FROM node:alpine as web-builder
|
||||||
ARG JELLYFIN_WEB_VERSION=v10.5.0
|
ARG JELLYFIN_WEB_VERSION=master
|
||||||
RUN apk add curl \
|
RUN apk add curl \
|
||||||
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||||
&& cd jellyfin-web-* \
|
&& cd jellyfin-web-* \
|
||||||
@ -17,8 +17,6 @@ FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
|||||||
WORKDIR /repo
|
WORKDIR /repo
|
||||||
COPY . .
|
COPY . .
|
||||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
# TODO Remove or update the sed line when we update dotnet version.
|
|
||||||
RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
|
|
||||||
# Discard objs - may cause failures if exists
|
# Discard objs - may cause failures if exists
|
||||||
RUN find . -type d -name obj | xargs -r rm -r
|
RUN find . -type d -name obj | xargs -r rm -r
|
||||||
# Build
|
# 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 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
|
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
&& 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=builder /jellyfin /jellyfin
|
||||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||||
|
|
||||||
|
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||||
|
|
||||||
EXPOSE 8096
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
VOLUME /cache /config /media
|
||||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||||
--datadir /config \
|
"--datadir", "/config", \
|
||||||
--cachedir /cache \
|
"--cachedir", "/cache", \
|
||||||
--ffmpeg /usr/bin/ffmpeg
|
"--ffmpeg", "/usr/bin/ffmpeg"]
|
||||||
|
@ -6,6 +6,7 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Main;
|
using Emby.Dlna.Main;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Services;
|
using MediaBrowser.Model.Services;
|
||||||
@ -108,12 +109,13 @@ namespace Emby.Dlna.Api
|
|||||||
|
|
||||||
public class DlnaServerService : IService, IRequiresRequest
|
public class DlnaServerService : IService, IRequiresRequest
|
||||||
{
|
{
|
||||||
private readonly IDlnaManager _dlnaManager;
|
|
||||||
|
|
||||||
private const string XMLContentType = "text/xml; charset=UTF-8";
|
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; }
|
public IRequest Request { get; set; }
|
||||||
private IHttpResultFactory _resultFactory;
|
|
||||||
|
|
||||||
private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory;
|
private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory;
|
||||||
|
|
||||||
@ -121,10 +123,14 @@ namespace Emby.Dlna.Api
|
|||||||
|
|
||||||
private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar;
|
private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar;
|
||||||
|
|
||||||
public DlnaServerService(IDlnaManager dlnaManager, IHttpResultFactory httpResultFactory)
|
public DlnaServerService(
|
||||||
|
IDlnaManager dlnaManager,
|
||||||
|
IHttpResultFactory httpResultFactory,
|
||||||
|
IServerConfigurationManager configurationManager)
|
||||||
{
|
{
|
||||||
_dlnaManager = dlnaManager;
|
_dlnaManager = dlnaManager;
|
||||||
_resultFactory = httpResultFactory;
|
_resultFactory = httpResultFactory;
|
||||||
|
_configurationManager = configurationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetHeader(string name)
|
private string GetHeader(string name)
|
||||||
@ -205,19 +211,32 @@ namespace Emby.Dlna.Api
|
|||||||
var pathInfo = Parse(Request.PathInfo);
|
var pathInfo = Parse(Request.PathInfo);
|
||||||
var first = pathInfo[0];
|
var first = pathInfo[0];
|
||||||
|
|
||||||
|
string baseUrl = _configurationManager.Configuration.BaseUrl;
|
||||||
|
|
||||||
// backwards compatibility
|
// backwards compatibility
|
||||||
// TODO: Work out what this is doing.
|
if (baseUrl.Length == 0)
|
||||||
if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) ||
|
{
|
||||||
string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase)
|
||||||
string.Equals(first, "jellyfin", StringComparison.OrdinalIgnoreCase))
|
|| string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (string.Equals(first, baseUrl.Remove(0, 1)))
|
||||||
{
|
{
|
||||||
index++;
|
index++;
|
||||||
|
var second = pathInfo[1];
|
||||||
|
if (string.Equals(second, "mediabrowser", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(second, "emby", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pathInfo[index];
|
return pathInfo[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> Parse(string pathUri)
|
private static string[] Parse(string pathUri)
|
||||||
{
|
{
|
||||||
var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None);
|
var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None);
|
||||||
|
|
||||||
@ -231,7 +250,7 @@ namespace Emby.Dlna.Api
|
|||||||
|
|
||||||
var args = pathInfo.Split('/');
|
var args = pathInfo.Split('/');
|
||||||
|
|
||||||
return args.Skip(1).ToList();
|
return args.Skip(1).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetIcon request)
|
public object Get(GetIcon request)
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
@ -160,7 +160,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
uuid = location.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
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();
|
var controller = sessionInfo.SessionControllers.OfType<PlayToController>().FirstOrDefault();
|
||||||
|
|
||||||
|
@ -4,8 +4,6 @@ using System.Linq;
|
|||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Net;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Rssdp;
|
using Rssdp;
|
||||||
using Rssdp.Infrastructure;
|
using Rssdp.Infrastructure;
|
||||||
|
|
||||||
@ -15,13 +13,14 @@ namespace Emby.Dlna.Ssdp
|
|||||||
{
|
{
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
|
||||||
private event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal;
|
private event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal;
|
||||||
|
|
||||||
private int _listenerCount;
|
private int _listenerCount;
|
||||||
private object _syncLock = new object();
|
private object _syncLock = new object();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered
|
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered
|
||||||
{
|
{
|
||||||
add
|
add
|
||||||
@ -31,6 +30,7 @@ namespace Emby.Dlna.Ssdp
|
|||||||
_listenerCount++;
|
_listenerCount++;
|
||||||
DeviceDiscoveredInternal += value;
|
DeviceDiscoveredInternal += value;
|
||||||
}
|
}
|
||||||
|
|
||||||
StartInternal();
|
StartInternal();
|
||||||
}
|
}
|
||||||
remove
|
remove
|
||||||
@ -43,21 +43,16 @@ namespace Emby.Dlna.Ssdp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
|
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
|
||||||
|
|
||||||
private SsdpDeviceLocator _deviceLocator;
|
private SsdpDeviceLocator _deviceLocator;
|
||||||
|
|
||||||
private readonly ISocketFactory _socketFactory;
|
|
||||||
private ISsdpCommunicationsServer _commsServer;
|
private ISsdpCommunicationsServer _commsServer;
|
||||||
|
|
||||||
public DeviceDiscovery(
|
public DeviceDiscovery(IServerConfigurationManager config)
|
||||||
ILoggerFactory loggerFactory,
|
|
||||||
IServerConfigurationManager config,
|
|
||||||
ISocketFactory socketFactory)
|
|
||||||
{
|
{
|
||||||
_logger = loggerFactory.CreateLogger(nameof(DeviceDiscovery));
|
|
||||||
_config = config;
|
_config = config;
|
||||||
_socketFactory = socketFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call this method from somewhere in your code to start the search.
|
// Call this method from somewhere in your code to start the search.
|
||||||
@ -82,8 +77,8 @@ namespace Emby.Dlna.Ssdp
|
|||||||
//_DeviceLocator.NotificationFilter = "upnp:rootdevice";
|
//_DeviceLocator.NotificationFilter = "upnp:rootdevice";
|
||||||
|
|
||||||
// Connect our event handler so we process devices as they are found
|
// Connect our event handler so we process devices as they are found
|
||||||
_deviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable;
|
_deviceLocator.DeviceAvailable += OnDeviceLocatorDeviceAvailable;
|
||||||
_deviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable;
|
_deviceLocator.DeviceUnavailable += OnDeviceLocatorDeviceUnavailable;
|
||||||
|
|
||||||
var dueTime = TimeSpan.FromSeconds(5);
|
var dueTime = TimeSpan.FromSeconds(5);
|
||||||
var interval = TimeSpan.FromSeconds(_config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds);
|
var interval = TimeSpan.FromSeconds(_config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds);
|
||||||
@ -94,7 +89,7 @@ namespace Emby.Dlna.Ssdp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process each found device in the event handler
|
// 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;
|
var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
|
||||||
|
|
||||||
@ -115,7 +110,7 @@ namespace Emby.Dlna.Ssdp
|
|||||||
DeviceDiscoveredInternal?.Invoke(this, args);
|
DeviceDiscoveredInternal?.Invoke(this, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void _DeviceLocator_DeviceUnavailable(object sender, DeviceUnavailableEventArgs e)
|
private void OnDeviceLocatorDeviceUnavailable(object sender, DeviceUnavailableEventArgs e)
|
||||||
{
|
{
|
||||||
var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
|
var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
@ -17,9 +17,4 @@
|
|||||||
<Compile Include="..\SharedVersion.cs" />
|
<Compile Include="..\SharedVersion.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<!-- We need at least C# 7.1 for the "default literal" feature-->
|
|
||||||
<LangVersion>latest</LangVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -3,7 +3,7 @@ using System;
|
|||||||
namespace Emby.Naming.AudioBook
|
namespace Emby.Naming.AudioBook
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a single video file
|
/// Represents a single video file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AudioBookFileInfo : IComparable<AudioBookFileInfo>
|
public class AudioBookFileInfo : IComparable<AudioBookFileInfo>
|
||||||
{
|
{
|
||||||
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||||||
namespace Emby.Naming.AudioBook
|
namespace Emby.Naming.AudioBook
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a complete video, including all parts and subtitles
|
/// Represents a complete video, including all parts and subtitles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AudioBookInfo
|
public class AudioBookInfo
|
||||||
{
|
{
|
||||||
|
@ -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]))?)([^\\\\/]*)$")
|
new EpisodeExpression("[\\\\/\\._ \\[\\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$")
|
||||||
{
|
{
|
||||||
SupportsAbsoluteEpisodeNumbers = true
|
SupportsAbsoluteEpisodeNumbers = true
|
||||||
@ -328,28 +336,33 @@ namespace Emby.Naming.Common
|
|||||||
|
|
||||||
// *** End Kodi Standard Naming
|
// *** 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
|
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
|
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
|
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
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
// "01.avi"
|
// "01.avi"
|
||||||
new EpisodeExpression(@".*[\\\/](?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\.\w+$")
|
new EpisodeExpression(@".*[\\\/](?<epnumber>\d+)(-(?<endingepnumber>\d+))*\.\w+$")
|
||||||
{
|
{
|
||||||
IsOptimistic = true,
|
IsOptimistic = true,
|
||||||
IsNamed = 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})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<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)
|
}.Select(i => new EpisodeExpression(i)
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
VideoFileExtensions = extensions
|
VideoFileExtensions = extensions
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -18,14 +19,14 @@
|
|||||||
<PackageId>Jellyfin.Naming</PackageId>
|
<PackageId>Jellyfin.Naming</PackageId>
|
||||||
<PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
|
<PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Code analysers-->
|
<!-- Code analysers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4" />
|
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" PrivateAssets="All" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
@ -25,7 +25,7 @@ namespace Emby.Naming.TV
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
private static readonly string[] _seasonFolderNames =
|
private static readonly string[] _seasonFolderNames =
|
||||||
{
|
{
|
||||||
@ -124,7 +124,7 @@ namespace Emby.Naming.TV
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <returns>System.Nullable{System.Int32}.</returns>
|
/// <returns>System.Nullable{System.Int32}.</returns>
|
||||||
|
@ -8,7 +8,7 @@ using Emby.Naming.Common;
|
|||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// http://kodi.wiki/view/Advancedsettings.xml#video
|
/// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CleanDateTimeParser
|
public class CleanDateTimeParser
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a single video file
|
/// Represents a single video file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class VideoFileInfo
|
public class VideoFileInfo
|
||||||
{
|
{
|
||||||
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a complete video, including all parts and subtitles
|
/// Represents a complete video, including all parts and subtitles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class VideoInfo
|
public class VideoInfo
|
||||||
{
|
{
|
||||||
|
@ -41,7 +41,7 @@ namespace Emby.Naming.Video
|
|||||||
/// <param name="isDirectory">if set to <c>true</c> [is folder].</param>
|
/// <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>
|
/// <param name="parseName">Whether or not the name should be parsed for info</param>
|
||||||
/// <returns>VideoFileInfo.</returns>
|
/// <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)
|
public VideoFileInfo Resolve(string path, bool isDirectory, bool parseName = true)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
<!-- Code analysers-->
|
<!-- Code analysers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<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="StyleCop.Analyzers" Version="1.1.118" />
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -616,8 +616,8 @@ namespace Emby.Server.Implementations.Activity
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a string description of a time-span value.
|
/// Constructs a string description of a time-span value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">The value of this item</param>
|
/// <param name="value">The value of this item.</param>
|
||||||
/// <param name="description">The name of this item (singular form)</param>
|
/// <param name="description">The name of this item (singular form).</param>
|
||||||
private static string CreateValueString(int value, string description)
|
private static string CreateValueString(int value, string description)
|
||||||
{
|
{
|
||||||
return string.Format(
|
return string.Format(
|
||||||
|
@ -4,7 +4,6 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Events;
|
using MediaBrowser.Common.Events;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
@ -16,7 +15,7 @@ using Microsoft.Extensions.Logging;
|
|||||||
namespace Emby.Server.Implementations.AppBase
|
namespace Emby.Server.Implementations.AppBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class BaseConfigurationManager
|
/// Class BaseConfigurationManager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class BaseConfigurationManager : IConfigurationManager
|
public abstract class BaseConfigurationManager : IConfigurationManager
|
||||||
{
|
{
|
||||||
@ -35,7 +34,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _configuration sync lock.
|
/// The _configuration sync lock.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private object _configurationSyncLock = new object();
|
private readonly object _configurationSyncLock = new object();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _configuration.
|
/// The _configuration.
|
||||||
@ -48,7 +47,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
/// <param name="applicationPaths">The application paths.</param>
|
/// <param name="applicationPaths">The application paths.</param>
|
||||||
/// <param name="loggerFactory">The logger factory.</param>
|
/// <param name="loggerFactory">The logger factory.</param>
|
||||||
/// <param name="xmlSerializer">The XML serializer.</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)
|
protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
CommonApplicationPaths = applicationPaths;
|
CommonApplicationPaths = applicationPaths;
|
||||||
@ -85,6 +84,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The logger.</value>
|
/// <value>The logger.</value>
|
||||||
protected ILogger Logger { get; private set; }
|
protected ILogger Logger { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the XML serializer.
|
/// Gets the XML serializer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -92,23 +92,39 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
protected IXmlSerializer XmlSerializer { get; private set; }
|
protected IXmlSerializer XmlSerializer { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the application paths.
|
/// Gets the application paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The application paths.</value>
|
/// <value>The application paths.</value>
|
||||||
public IApplicationPaths CommonApplicationPaths { get; private set; }
|
public IApplicationPaths CommonApplicationPaths { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the system configuration
|
/// Gets or sets the system configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The configuration.</value>
|
/// <value>The configuration.</value>
|
||||||
public BaseApplicationConfiguration CommonConfiguration
|
public BaseApplicationConfiguration CommonConfiguration
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
// Lazy load
|
if (_configurationLoaded)
|
||||||
LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer));
|
{
|
||||||
return _configuration;
|
return _configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_configurationSyncLock)
|
||||||
|
{
|
||||||
|
if (_configurationLoaded)
|
||||||
|
{
|
||||||
|
return _configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
_configuration = (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer);
|
||||||
|
|
||||||
|
_configurationLoaded = true;
|
||||||
|
|
||||||
|
return _configuration;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected set
|
protected set
|
||||||
{
|
{
|
||||||
_configuration = value;
|
_configuration = value;
|
||||||
@ -158,7 +174,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
/// Replaces the configuration.
|
/// Replaces the configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="newConfiguration">The new configuration.</param>
|
/// <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)
|
public virtual void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration)
|
||||||
{
|
{
|
||||||
if (newConfiguration == null)
|
if (newConfiguration == null)
|
||||||
@ -201,7 +217,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
cachePath = CommonConfiguration.CachePath;
|
cachePath = CommonConfiguration.CachePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogInformation("Setting cache path to " + cachePath);
|
Logger.LogInformation("Setting cache path: {Path}", cachePath);
|
||||||
((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath;
|
((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +225,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
/// Replaces the cache path.
|
/// Replaces the cache path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="newConfig">The new configuration.</param>
|
/// <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)
|
private void ValidateCachePath(BaseApplicationConfiguration newConfig)
|
||||||
{
|
{
|
||||||
var newPath = newConfig.CachePath;
|
var newPath = newConfig.CachePath;
|
||||||
@ -220,7 +236,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
// Validate
|
// Validate
|
||||||
if (!Directory.Exists(newPath))
|
if (!Directory.Exists(newPath))
|
||||||
{
|
{
|
||||||
throw new FileNotFoundException(
|
throw new DirectoryNotFoundException(
|
||||||
string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"{0} does not exist.",
|
"{0} does not exist.",
|
||||||
@ -299,8 +315,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
throw new ArgumentException("Expected configuration type is " + configurationType.Name);
|
throw new ArgumentException("Expected configuration type is " + configurationType.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
var validatingStore = configurationStore as IValidatingConfiguration;
|
if (configurationStore is IValidatingConfiguration validatingStore)
|
||||||
if (validatingStore != null)
|
|
||||||
{
|
{
|
||||||
var currentConfiguration = GetConfiguration(key);
|
var currentConfiguration = GetConfiguration(key);
|
||||||
|
|
||||||
|
@ -6,13 +6,13 @@ using MediaBrowser.Model.Serialization;
|
|||||||
namespace Emby.Server.Implementations.AppBase
|
namespace Emby.Server.Implementations.AppBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class ConfigurationHelper
|
/// Class ConfigurationHelper.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ConfigurationHelper
|
public static class ConfigurationHelper
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads an xml configuration file from the file system
|
/// 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>
|
/// </summary>
|
||||||
/// <param name="type">The type.</param>
|
/// <param name="type">The type.</param>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
|
@ -88,7 +88,6 @@ using MediaBrowser.Model.Cryptography;
|
|||||||
using MediaBrowser.Model.Diagnostics;
|
using MediaBrowser.Model.Diagnostics;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
@ -110,9 +109,8 @@ using Microsoft.AspNetCore.Http;
|
|||||||
using Microsoft.AspNetCore.Http.Extensions;
|
using Microsoft.AspNetCore.Http.Extensions;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using ServiceStack;
|
using Microsoft.OpenApi.Models;
|
||||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations
|
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>
|
/// <summary>
|
||||||
/// Gets the server configuration manager.
|
/// Gets the server configuration manager.
|
||||||
@ -321,7 +337,7 @@ namespace Emby.Server.Implementations
|
|||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the installation manager.
|
/// Gets the installation manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The installation manager.</value>
|
/// <value>The installation manager.</value>
|
||||||
protected IInstallationManager InstallationManager { get; private set; }
|
protected IInstallationManager InstallationManager { get; private set; }
|
||||||
@ -362,7 +378,7 @@ namespace Emby.Server.Implementations
|
|||||||
{
|
{
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
|
|
||||||
XmlSerializer = new MyXmlSerializer(fileSystem, loggerFactory);
|
XmlSerializer = new MyXmlSerializer();
|
||||||
|
|
||||||
NetworkManager = networkManager;
|
NetworkManager = networkManager;
|
||||||
networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
|
networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
|
||||||
@ -410,13 +426,17 @@ namespace Emby.Server.Implementations
|
|||||||
_validAddressResults.Clear();
|
_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>
|
/// <summary>
|
||||||
/// Gets the current application user agent.
|
/// Gets the current application user agent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The application user agent.</value>
|
/// <value>The application user agent.</value>
|
||||||
public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersion;
|
public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersionString;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the email address for use within a comment section of a user agent field.
|
/// 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;
|
public string Name => ApplicationProductName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an instance of type and resolves all constructor dependencies
|
/// Creates an instance of type and resolves all constructor dependencies.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="type">The type.</param>
|
/// <param name="type">The type.</param>
|
||||||
/// <returns>System.Object.</returns>
|
/// <returns>System.Object.</returns>
|
||||||
public object CreateInstance(Type type)
|
public object CreateInstance(Type type)
|
||||||
=> ActivatorUtilities.CreateInstance(_serviceProvider, type);
|
=> ActivatorUtilities.CreateInstance(ServiceProvider, type);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an instance of type and resolves all constructor dependencies
|
/// Creates an instance of type and resolves all constructor dependencies.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// /// <typeparam name="T">The type.</typeparam>
|
/// /// <typeparam name="T">The type.</typeparam>
|
||||||
/// <returns>T.</returns>
|
/// <returns>T.</returns>
|
||||||
public T CreateInstance<T>()
|
public T CreateInstance<T>()
|
||||||
=> ActivatorUtilities.CreateInstance<T>(_serviceProvider);
|
=> ActivatorUtilities.CreateInstance<T>(ServiceProvider);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the instance safe.
|
/// Creates the instance safe.
|
||||||
@ -477,7 +497,7 @@ namespace Emby.Server.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Creating instance of {Type}", type);
|
Logger.LogDebug("Creating instance of {Type}", type);
|
||||||
return ActivatorUtilities.CreateInstance(_serviceProvider, type);
|
return ActivatorUtilities.CreateInstance(ServiceProvider, type);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -491,12 +511,12 @@ namespace Emby.Server.Implementations
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type</typeparam>
|
/// <typeparam name="T">The type</typeparam>
|
||||||
/// <returns>``0.</returns>
|
/// <returns>``0.</returns>
|
||||||
public T Resolve<T>() => _serviceProvider.GetService<T>();
|
public T Resolve<T>() => ServiceProvider.GetService<T>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the export types.
|
/// Gets the export types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type</typeparam>
|
/// <typeparam name="T">The type.</typeparam>
|
||||||
/// <returns>IEnumerable{Type}.</returns>
|
/// <returns>IEnumerable{Type}.</returns>
|
||||||
public IEnumerable<Type> GetExportTypes<T>()
|
public IEnumerable<Type> GetExportTypes<T>()
|
||||||
{
|
{
|
||||||
@ -508,11 +528,12 @@ namespace Emby.Server.Implementations
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IReadOnlyCollection<T> GetExports<T>(bool manageLifetime = true)
|
public IReadOnlyCollection<T> GetExports<T>(bool manageLifetime = true)
|
||||||
{
|
{
|
||||||
|
// Convert to list so this isn't executed for each iteration
|
||||||
var parts = GetExportTypes<T>()
|
var parts = GetExportTypes<T>()
|
||||||
.Select(CreateInstanceSafe)
|
.Select(CreateInstanceSafe)
|
||||||
.Where(i => i != null)
|
.Where(i => i != null)
|
||||||
.Cast<T>()
|
.Cast<T>()
|
||||||
.ToList(); // Convert to list so this isn't executed for each iteration
|
.ToList();
|
||||||
|
|
||||||
if (manageLifetime)
|
if (manageLifetime)
|
||||||
{
|
{
|
||||||
@ -607,77 +628,14 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
await RegisterResources(serviceCollection).ConfigureAwait(false);
|
await RegisterResources(serviceCollection).ConfigureAwait(false);
|
||||||
|
|
||||||
FindParts();
|
ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath;
|
||||||
|
if (string.IsNullOrEmpty(ContentRoot))
|
||||||
string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath;
|
|
||||||
if (string.IsNullOrEmpty(contentRoot))
|
|
||||||
{
|
{
|
||||||
contentRoot = ServerConfigurationManager.ApplicationPaths.WebPath;
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
|
public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
|
||||||
{
|
{
|
||||||
if (!context.WebSockets.IsWebSocketRequest)
|
if (!context.WebSockets.IsWebSocketRequest)
|
||||||
{
|
{
|
||||||
@ -688,7 +646,7 @@ namespace Emby.Server.Implementations
|
|||||||
await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false);
|
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)
|
if (context.WebSockets.IsWebSocketRequest)
|
||||||
{
|
{
|
||||||
@ -749,7 +707,8 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
|
serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
|
||||||
|
|
||||||
serviceCollection.AddSingleton(typeof(ICryptoProvider), typeof(CryptographyProvider));
|
var cryptoProvider = new CryptographyProvider();
|
||||||
|
serviceCollection.AddSingleton<ICryptoProvider>(cryptoProvider);
|
||||||
|
|
||||||
SocketFactory = new SocketFactory();
|
SocketFactory = new SocketFactory();
|
||||||
serviceCollection.AddSingleton(SocketFactory);
|
serviceCollection.AddSingleton(SocketFactory);
|
||||||
@ -788,7 +747,17 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
_userRepository = GetUserRepository();
|
_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);
|
serviceCollection.AddSingleton(UserManager);
|
||||||
|
|
||||||
@ -866,8 +835,7 @@ namespace Emby.Server.Implementations
|
|||||||
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
|
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
|
||||||
serviceCollection.AddSingleton(NotificationManager);
|
serviceCollection.AddSingleton(NotificationManager);
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IDeviceDiscovery>(
|
serviceCollection.AddSingleton<IDeviceDiscovery>(new DeviceDiscovery(ServerConfigurationManager));
|
||||||
new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory));
|
|
||||||
|
|
||||||
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
|
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
|
||||||
serviceCollection.AddSingleton(ChapterManager);
|
serviceCollection.AddSingleton(ChapterManager);
|
||||||
@ -896,7 +864,7 @@ namespace Emby.Server.Implementations
|
|||||||
serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
|
serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
|
||||||
serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
|
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);
|
serviceCollection.AddSingleton(AuthService);
|
||||||
|
|
||||||
SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory);
|
SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory);
|
||||||
@ -906,7 +874,7 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
_displayPreferencesRepository.Initialize();
|
_displayPreferencesRepository.Initialize();
|
||||||
|
|
||||||
var userDataRepo = new SqliteUserDataRepository(LoggerFactory, ApplicationPaths);
|
var userDataRepo = new SqliteUserDataRepository(LoggerFactory.CreateLogger<SqliteUserDataRepository>(), ApplicationPaths);
|
||||||
|
|
||||||
SetStaticProperties();
|
SetStaticProperties();
|
||||||
|
|
||||||
@ -915,8 +883,6 @@ namespace Emby.Server.Implementations
|
|||||||
((UserDataManager)UserDataManager).Repository = userDataRepo;
|
((UserDataManager)UserDataManager).Repository = userDataRepo;
|
||||||
ItemRepository.Initialize(userDataRepo, UserManager);
|
ItemRepository.Initialize(userDataRepo, UserManager);
|
||||||
((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
|
((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
|
||||||
|
|
||||||
_serviceProvider = serviceCollection.BuildServiceProvider();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths)
|
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths)
|
||||||
@ -1007,7 +973,7 @@ namespace Emby.Server.Implementations
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dirty hacks
|
/// Dirty hacks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void SetStaticProperties()
|
private void SetStaticProperties()
|
||||||
{
|
{
|
||||||
@ -1073,9 +1039,9 @@ namespace Emby.Server.Implementations
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the parts.
|
/// Finds the parts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected void FindParts()
|
public void FindParts()
|
||||||
{
|
{
|
||||||
InstallationManager = _serviceProvider.GetService<IInstallationManager>();
|
InstallationManager = ServiceProvider.GetService<IInstallationManager>();
|
||||||
InstallationManager.PluginInstalled += PluginInstalled;
|
InstallationManager.PluginInstalled += PluginInstalled;
|
||||||
|
|
||||||
if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
|
if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
|
||||||
@ -1204,7 +1170,7 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
private CertificateInfo CertificateInfo { get; set; }
|
private CertificateInfo CertificateInfo { get; set; }
|
||||||
|
|
||||||
protected X509Certificate2 Certificate { get; private set; }
|
public X509Certificate2 Certificate { get; private set; }
|
||||||
|
|
||||||
private IEnumerable<string> GetUrlPrefixes()
|
private IEnumerable<string> GetUrlPrefixes()
|
||||||
{
|
{
|
||||||
@ -1415,17 +1381,18 @@ namespace Emby.Server.Implementations
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the system status.
|
/// Gets the system status.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cancellationToken">The cancellation token</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>SystemInfo.</returns>
|
/// <returns>SystemInfo.</returns>
|
||||||
public async Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken)
|
public async Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
|
var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
|
||||||
|
var transcodingTempPath = ConfigurationManager.GetTranscodePath();
|
||||||
|
|
||||||
return new SystemInfo
|
return new SystemInfo
|
||||||
{
|
{
|
||||||
HasPendingRestart = HasPendingRestart,
|
HasPendingRestart = HasPendingRestart,
|
||||||
IsShuttingDown = IsShuttingDown,
|
IsShuttingDown = IsShuttingDown,
|
||||||
Version = ApplicationVersion,
|
Version = ApplicationVersionString,
|
||||||
WebSocketPortNumber = HttpPort,
|
WebSocketPortNumber = HttpPort,
|
||||||
CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(),
|
CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(),
|
||||||
Id = SystemId,
|
Id = SystemId,
|
||||||
@ -1443,7 +1410,7 @@ namespace Emby.Server.Implementations
|
|||||||
CanSelfRestart = CanSelfRestart,
|
CanSelfRestart = CanSelfRestart,
|
||||||
CanLaunchWebBrowser = CanLaunchWebBrowser,
|
CanLaunchWebBrowser = CanLaunchWebBrowser,
|
||||||
HasUpdateAvailable = HasUpdateAvailable,
|
HasUpdateAvailable = HasUpdateAvailable,
|
||||||
TranscodingTempPath = ApplicationPaths.TranscodingTempPath,
|
TranscodingTempPath = transcodingTempPath,
|
||||||
ServerName = FriendlyName,
|
ServerName = FriendlyName,
|
||||||
LocalAddress = localAddress,
|
LocalAddress = localAddress,
|
||||||
SupportsLibraryMonitor = true,
|
SupportsLibraryMonitor = true,
|
||||||
@ -1465,7 +1432,7 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
return new PublicSystemInfo
|
return new PublicSystemInfo
|
||||||
{
|
{
|
||||||
Version = ApplicationVersion,
|
Version = ApplicationVersionString,
|
||||||
ProductName = ApplicationProductName,
|
ProductName = ApplicationProductName,
|
||||||
Id = SystemId,
|
Id = SystemId,
|
||||||
OperatingSystem = OperatingSystem.Id.ToString(),
|
OperatingSystem = OperatingSystem.Id.ToString(),
|
||||||
@ -1588,7 +1555,7 @@ namespace Emby.Server.Implementations
|
|||||||
return resultList;
|
return resultList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IPAddress NormalizeConfiguredLocalAddress(string address)
|
public IPAddress NormalizeConfiguredLocalAddress(string address)
|
||||||
{
|
{
|
||||||
var index = address.Trim('/').IndexOf('/');
|
var index = address.Trim('/').IndexOf('/');
|
||||||
|
|
||||||
@ -1664,10 +1631,6 @@ namespace Emby.Server.Implementations
|
|||||||
? Environment.MachineName
|
? Environment.MachineName
|
||||||
: ServerConfigurationManager.Configuration.ServerName;
|
: ServerConfigurationManager.Configuration.ServerName;
|
||||||
|
|
||||||
public int HttpPort { get; private set; }
|
|
||||||
|
|
||||||
public int HttpsPort { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shuts down.
|
/// Shuts down.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -1730,7 +1693,7 @@ namespace Emby.Server.Implementations
|
|||||||
/// dns is prefixed with a valid Uri prefix.
|
/// dns is prefixed with a valid Uri prefix.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="externalDns">The external dns prefix to get the hostname of.</param>
|
/// <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)
|
private static string GetHostnameFromExternalDns(string externalDns)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(externalDns))
|
if (string.IsNullOrEmpty(externalDns))
|
||||||
@ -1844,6 +1807,7 @@ namespace Emby.Server.Implementations
|
|||||||
internal class CertificateInfo
|
internal class CertificateInfo
|
||||||
{
|
{
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
|
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -470,10 +470,10 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
_libraryManager.CreateItem(item, null);
|
_libraryManager.CreateItem(item, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
{
|
{
|
||||||
ForceSave = !isNew && forceUpdate
|
ForceSave = !isNew && forceUpdate
|
||||||
}, cancellationToken);
|
}, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@ -636,7 +636,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
private async Task RefreshLatestChannelItems(IChannel channel, CancellationToken cancellationToken)
|
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();
|
var query = new InternalItemsQuery();
|
||||||
query.Parent = internalChannel;
|
query.Parent = internalChannel;
|
||||||
@ -1156,7 +1156,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime))
|
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;
|
return item;
|
||||||
|
@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
// This could cause it to get re-resolved as a plain folder
|
// This could cause it to get re-resolved as a plain folder
|
||||||
var folderName = _fileSystem.GetValidFilename(name) + " [boxset]";
|
var folderName = _fileSystem.GetValidFilename(name) + " [boxset]";
|
||||||
|
|
||||||
var parentFolder = GetCollectionsFolder(true).Result;
|
var parentFolder = GetCollectionsFolder(true).GetAwaiter().GetResult();
|
||||||
|
|
||||||
if (parentFolder == null)
|
if (parentFolder == null)
|
||||||
{
|
{
|
||||||
@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
|
|
||||||
if (options.ItemIdList.Length > 0)
|
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
|
// The initial adding of items is going to create a local metadata file
|
||||||
// This will cause internet metadata to be skipped as a result
|
// This will cause internet metadata to be skipped as a result
|
||||||
@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
}
|
}
|
||||||
else
|
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
|
CollectionCreated?.Invoke(this, new CollectionCreatedEventArgs
|
||||||
@ -178,12 +178,12 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
|
|
||||||
public void AddToCollection(Guid collectionId, IEnumerable<string> ids)
|
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)
|
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)
|
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);
|
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
|
ForceSave = true
|
||||||
}, RefreshPriority.High);
|
}, RefreshPriority.High);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Emby.Server.Implementations.AppBase;
|
using Emby.Server.Implementations.AppBase;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
@ -14,7 +13,7 @@ using Microsoft.Extensions.Logging;
|
|||||||
namespace Emby.Server.Implementations.Configuration
|
namespace Emby.Server.Implementations.Configuration
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class ServerConfigurationManager
|
/// Class ServerConfigurationManager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ServerConfigurationManager : BaseConfigurationManager, IServerConfigurationManager
|
public class ServerConfigurationManager : BaseConfigurationManager, IServerConfigurationManager
|
||||||
{
|
{
|
||||||
@ -62,13 +61,6 @@ namespace Emby.Server.Implementations.Configuration
|
|||||||
base.OnConfigurationUpdated();
|
base.OnConfigurationUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void AddParts(IEnumerable<IConfigurationFactory> factories)
|
|
||||||
{
|
|
||||||
base.AddParts(factories);
|
|
||||||
|
|
||||||
UpdateTranscodePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the metadata path.
|
/// Updates the metadata path.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Replaces the configuration.
|
/// Replaces the configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -123,7 +93,6 @@ namespace Emby.Server.Implementations.Configuration
|
|||||||
base.ReplaceConfiguration(newConfiguration);
|
base.ReplaceConfiguration(newConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Validates the SSL certificate.
|
/// Validates the SSL certificate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -30,6 +30,9 @@ namespace Emby.Server.Implementations.Cryptography
|
|||||||
|
|
||||||
private bool _disposed = false;
|
private bool _disposed = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CryptographyProvider"/> class.
|
||||||
|
/// </summary>
|
||||||
public CryptographyProvider()
|
public CryptographyProvider()
|
||||||
{
|
{
|
||||||
// FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
|
// 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}");
|
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)
|
public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt)
|
||||||
{
|
{
|
||||||
if (hashMethod == DefaultHashMethod)
|
if (hashMethod == DefaultHashMethod)
|
||||||
@ -90,7 +87,6 @@ namespace Emby.Server.Implementations.Cryptography
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
|
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
|
public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
|
||||||
|
@ -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)"))
|
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("@id", new Guid(displayPreferences.Id).ToByteArray());
|
||||||
statement.TryBind("@userId", userId.ToGuidBlob());
|
statement.TryBind("@userId", userId.ToByteArray());
|
||||||
statement.TryBind("@client", client);
|
statement.TryBind("@client", client);
|
||||||
statement.TryBind("@data", serialized);
|
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"))
|
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("@id", guidId.ToByteArray());
|
||||||
statement.TryBind("@userId", userId.ToGuidBlob());
|
statement.TryBind("@userId", userId.ToByteArray());
|
||||||
statement.TryBind("@client", client);
|
statement.TryBind("@client", client);
|
||||||
|
|
||||||
foreach (var row in statement.ExecuteQuery())
|
foreach (var row in statement.ExecuteQuery())
|
||||||
@ -200,7 +200,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
using (var connection = GetConnection(true))
|
using (var connection = GetConnection(true))
|
||||||
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId"))
|
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())
|
foreach (var row in statement.ExecuteQuery())
|
||||||
{
|
{
|
||||||
|
@ -9,6 +9,47 @@ namespace Emby.Server.Implementations.Data
|
|||||||
{
|
{
|
||||||
public static class SqliteExtensions
|
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)
|
public static void RunQueries(this SQLiteDatabaseConnection connection, string[] queries)
|
||||||
{
|
{
|
||||||
if (queries == null)
|
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)
|
public static Guid ReadGuidFromBlob(this IResultSetValue result)
|
||||||
{
|
{
|
||||||
return new Guid(result.ToBlob());
|
return new Guid(result.ToBlob());
|
||||||
@ -50,58 +81,16 @@ namespace Emby.Server.Implementations.Data
|
|||||||
CultureInfo.InvariantCulture);
|
CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetDateTimeKindFormat(
|
private static string GetDateTimeKindFormat(DateTimeKind kind)
|
||||||
DateTimeKind kind)
|
=> (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal;
|
||||||
{
|
|
||||||
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];
|
|
||||||
|
|
||||||
public static DateTime ReadDateTime(this IResultSetValue result)
|
public static DateTime ReadDateTime(this IResultSetValue result)
|
||||||
{
|
{
|
||||||
var dateText = result.ToString();
|
var dateText = result.ToString();
|
||||||
|
|
||||||
return DateTime.ParseExact(
|
return DateTime.ParseExact(
|
||||||
dateText, _datetimeFormats,
|
dateText,
|
||||||
|
_datetimeFormats,
|
||||||
DateTimeFormatInfo.InvariantInfo,
|
DateTimeFormatInfo.InvariantInfo,
|
||||||
DateTimeStyles.None).ToUniversalTime();
|
DateTimeStyles.None).ToUniversalTime();
|
||||||
}
|
}
|
||||||
@ -139,7 +128,10 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
public static void Attach(SQLiteDatabaseConnection db, string path, string alias)
|
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))
|
using (var statement = db.PrepareStatement(commandText))
|
||||||
{
|
{
|
||||||
@ -186,10 +178,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
private static void CheckName(string name)
|
private static void CheckName(string name)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
//if (!name.IndexOf("@", StringComparison.OrdinalIgnoreCase) != 0)
|
throw new ArgumentException("Invalid param name: " + name, nameof(name));
|
||||||
{
|
|
||||||
throw new Exception("Invalid param name: " + name);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,7 +253,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
{
|
{
|
||||||
if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
|
if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
|
||||||
{
|
{
|
||||||
bindParam.Bind(value.ToGuidBlob());
|
bindParam.Bind(value.ToByteArray());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -392,8 +381,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(
|
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement This)
|
||||||
this IStatement This)
|
|
||||||
{
|
{
|
||||||
while (This.MoveNext())
|
while (This.MoveNext())
|
||||||
{
|
{
|
||||||
|
@ -27,7 +27,6 @@ using MediaBrowser.Model.Entities;
|
|||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.LiveTv;
|
using MediaBrowser.Model.LiveTv;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using MediaBrowser.Model.Serialization;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SQLitePCL.pretty;
|
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"))
|
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.TryBind("@Images", SerializeImages(item));
|
||||||
|
|
||||||
saveImagesStatement.MoveNext();
|
saveImagesStatement.MoveNext();
|
||||||
@ -658,12 +657,14 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
private void SaveItem(BaseItem item, BaseItem topParent, string userDataKey, IStatement saveItemStatement)
|
private void SaveItem(BaseItem item, BaseItem topParent, string userDataKey, IStatement saveItemStatement)
|
||||||
{
|
{
|
||||||
saveItemStatement.TryBind("@guid", item.Id);
|
Type type = item.GetType();
|
||||||
saveItemStatement.TryBind("@type", item.GetType().FullName);
|
|
||||||
|
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
@ -1177,7 +1178,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
{
|
{
|
||||||
if (id == Guid.Empty)
|
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();
|
CheckDisposed();
|
||||||
@ -1988,7 +1989,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
throw new ArgumentNullException(nameof(chapters));
|
throw new ArgumentNullException(nameof(chapters));
|
||||||
}
|
}
|
||||||
|
|
||||||
var idBlob = id.ToGuidBlob();
|
var idBlob = id.ToByteArray();
|
||||||
|
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection())
|
||||||
{
|
{
|
||||||
@ -3760,7 +3761,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind(paramName, personId.ToGuidBlob());
|
statement.TryBind(paramName, personId.ToByteArray());
|
||||||
}
|
}
|
||||||
index++;
|
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))");
|
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind(paramName, artistId.ToGuidBlob());
|
statement.TryBind(paramName, artistId.ToByteArray());
|
||||||
}
|
}
|
||||||
index++;
|
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))");
|
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind(paramName, artistId.ToGuidBlob());
|
statement.TryBind(paramName, artistId.ToByteArray());
|
||||||
}
|
}
|
||||||
index++;
|
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))");
|
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)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind(paramName, artistId.ToGuidBlob());
|
statement.TryBind(paramName, artistId.ToByteArray());
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
@ -4028,7 +4029,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")");
|
clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")");
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind(paramName, albumId.ToGuidBlob());
|
statement.TryBind(paramName, albumId.ToByteArray());
|
||||||
}
|
}
|
||||||
index++;
|
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))");
|
clauses.Add("(guid not in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind(paramName, artistId.ToGuidBlob());
|
statement.TryBind(paramName, artistId.ToByteArray());
|
||||||
}
|
}
|
||||||
index++;
|
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))");
|
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind(paramName, genreId.ToGuidBlob());
|
statement.TryBind(paramName, genreId.ToByteArray());
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
@ -4137,7 +4138,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind(paramName, studioId.ToGuidBlob());
|
statement.TryBind(paramName, studioId.ToByteArray());
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
@ -4913,7 +4914,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
{
|
{
|
||||||
connection.RunInTransaction(db =>
|
connection.RunInTransaction(db =>
|
||||||
{
|
{
|
||||||
var idBlob = id.ToGuidBlob();
|
var idBlob = id.ToByteArray();
|
||||||
|
|
||||||
// Delete people
|
// Delete people
|
||||||
ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", idBlob);
|
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");
|
whereClauses.Add("ItemId=@ItemId");
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind("@ItemId", query.ItemId.ToGuidBlob());
|
statement.TryBind("@ItemId", query.ItemId.ToByteArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!query.AppearsInItemId.Equals(Guid.Empty))
|
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)");
|
whereClauses.Add("Name in (Select Name from People where ItemId=@AppearsInItemId)");
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToGuidBlob());
|
statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToByteArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList();
|
var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList();
|
||||||
@ -5109,7 +5110,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
|
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
var itemIdBlob = itemId.ToGuidBlob();
|
var itemIdBlob = itemId.ToByteArray();
|
||||||
|
|
||||||
// First delete
|
// First delete
|
||||||
deleteAncestorsStatement.Reset();
|
deleteAncestorsStatement.Reset();
|
||||||
@ -5143,7 +5144,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
|
|
||||||
var ancestorId = ancestorIds[i];
|
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));
|
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();
|
CheckDisposed();
|
||||||
|
|
||||||
var guidBlob = itemId.ToGuidBlob();
|
var guidBlob = itemId.ToByteArray();
|
||||||
|
|
||||||
// First delete
|
// First delete
|
||||||
db.Execute("delete from ItemValues where ItemId=@Id", guidBlob);
|
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)
|
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;
|
isSubsequentRow = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5688,7 +5692,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
{
|
{
|
||||||
connection.RunInTransaction(db =>
|
connection.RunInTransaction(db =>
|
||||||
{
|
{
|
||||||
var itemIdBlob = itemId.ToGuidBlob();
|
var itemIdBlob = itemId.ToByteArray();
|
||||||
|
|
||||||
// First delete chapters
|
// First delete chapters
|
||||||
db.Execute("delete from People where ItemId=@ItemId", itemIdBlob);
|
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))
|
using (var statement = PrepareStatement(connection, cmdText))
|
||||||
{
|
{
|
||||||
statement.TryBind("@ItemId", query.ItemId.ToGuidBlob());
|
statement.TryBind("@ItemId", query.ItemId.ToByteArray());
|
||||||
|
|
||||||
if (query.Type.HasValue)
|
if (query.Type.HasValue)
|
||||||
{
|
{
|
||||||
@ -5849,7 +5853,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
{
|
{
|
||||||
connection.RunInTransaction(db =>
|
connection.RunInTransaction(db =>
|
||||||
{
|
{
|
||||||
var itemIdBlob = id.ToGuidBlob();
|
var itemIdBlob = id.ToByteArray();
|
||||||
|
|
||||||
// First delete chapters
|
// First delete chapters
|
||||||
db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);
|
db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
@ -15,23 +14,19 @@ namespace Emby.Server.Implementations.Data
|
|||||||
public class SqliteUserDataRepository : BaseSqliteRepository, IUserDataRepository
|
public class SqliteUserDataRepository : BaseSqliteRepository, IUserDataRepository
|
||||||
{
|
{
|
||||||
public SqliteUserDataRepository(
|
public SqliteUserDataRepository(
|
||||||
ILoggerFactory loggerFactory,
|
ILogger<SqliteUserDataRepository> logger,
|
||||||
IApplicationPaths appPaths)
|
IApplicationPaths appPaths)
|
||||||
: base(loggerFactory.CreateLogger(nameof(SqliteUserDataRepository)))
|
: base(logger)
|
||||||
{
|
{
|
||||||
DbFilePath = Path.Combine(appPaths.DataPath, "library.db");
|
DbFilePath = Path.Combine(appPaths.DataPath, "library.db");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets the name of the repository
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
public string Name => "SQLite";
|
public string Name => "SQLite";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens the connection to the database
|
/// Opens the connection to the database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Task.</returns>
|
|
||||||
public void Initialize(IUserManager userManager, SemaphoreSlim dbLock, SQLiteDatabaseConnection dbConnection)
|
public void Initialize(IUserManager userManager, SemaphoreSlim dbLock, SQLiteDatabaseConnection dbConnection)
|
||||||
{
|
{
|
||||||
WriteLock.Dispose();
|
WriteLock.Dispose();
|
||||||
@ -97,7 +92,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
statement.TryBind("@UserId", user.Id.ToGuidBlob());
|
statement.TryBind("@UserId", user.Id.ToByteArray());
|
||||||
statement.TryBind("@InternalUserId", user.InternalId);
|
statement.TryBind("@InternalUserId", user.InternalId);
|
||||||
|
|
||||||
statement.MoveNext();
|
statement.MoveNext();
|
||||||
|
@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
{
|
{
|
||||||
using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @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.TryBind("@data", serialized);
|
||||||
|
|
||||||
statement.MoveNext();
|
statement.MoveNext();
|
||||||
|
@ -130,7 +130,6 @@ namespace Emby.Server.Implementations.Devices
|
|||||||
var session = _authRepo.Get(new AuthenticationInfoQuery
|
var session = _authRepo.Get(new AuthenticationInfoQuery
|
||||||
{
|
{
|
||||||
DeviceId = id
|
DeviceId = id
|
||||||
|
|
||||||
}).Items.FirstOrDefault();
|
}).Items.FirstOrDefault();
|
||||||
|
|
||||||
var device = session == null ? null : ToDeviceInfo(session);
|
var device = session == null ? null : ToDeviceInfo(session);
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
|
<ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
|
||||||
<ProjectReference Include="..\Emby.Notifications\Emby.Notifications.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.Model\MediaBrowser.Model.csproj" />
|
||||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
||||||
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
||||||
@ -10,7 +11,6 @@
|
|||||||
<ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj" />
|
<ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj" />
|
||||||
<ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj" />
|
<ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj" />
|
||||||
<ProjectReference Include="..\Emby.Dlna\Emby.Dlna.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.Api\MediaBrowser.Api.csproj" />
|
||||||
<ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj" />
|
<ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj" />
|
||||||
<ProjectReference Include="..\Emby.Photos\Emby.Photos.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.ResponseCompression" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" 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.AspNetCore.WebSockets" Version="2.2.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.0.1" />
|
||||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.6.0" />
|
<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="sharpcompress" Version="0.24.0" />
|
||||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" />
|
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" />
|
||||||
|
<PackageReference Include="System.Interactive.Async" Version="4.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -47,16 +50,12 @@
|
|||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<!-- We need at least C# 7.3 to compare tuples-->
|
|
||||||
<LangVersion>latest</LangVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<!-- Code analysers-->
|
<!-- Code analysers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<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" />
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
||||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||||
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
@ -15,209 +14,134 @@ using Mono.Nat;
|
|||||||
|
|
||||||
namespace Emby.Server.Implementations.EntryPoints
|
namespace Emby.Server.Implementations.EntryPoints
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Server entrypoint handling external port forwarding.
|
||||||
|
/// </summary>
|
||||||
public class ExternalPortForwarding : IServerEntryPoint
|
public class ExternalPortForwarding : IServerEntryPoint
|
||||||
{
|
{
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IHttpClient _httpClient;
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||||
|
|
||||||
|
private readonly object _createdRulesLock = new object();
|
||||||
|
private List<IPEndPoint> _createdRules = new List<IPEndPoint>();
|
||||||
private Timer _timer;
|
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;
|
_appHost = appHost;
|
||||||
_config = config;
|
_config = config;
|
||||||
_deviceDiscovery = deviceDiscovery;
|
_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()
|
private string GetConfigIdentifier()
|
||||||
{
|
{
|
||||||
var values = new List<string>();
|
const char Separator = '|';
|
||||||
var config = _config.Configuration;
|
var config = _config.Configuration;
|
||||||
|
|
||||||
values.Add(config.EnableUPnP.ToString());
|
return new StringBuilder(32)
|
||||||
values.Add(config.PublicPort.ToString(CultureInfo.InvariantCulture));
|
.Append(config.EnableUPnP).Append(Separator)
|
||||||
values.Add(_appHost.HttpPort.ToString(CultureInfo.InvariantCulture));
|
.Append(config.PublicPort).Append(Separator)
|
||||||
values.Add(_appHost.HttpsPort.ToString(CultureInfo.InvariantCulture));
|
.Append(_appHost.HttpPort).Append(Separator)
|
||||||
values.Add(_appHost.EnableHttps.ToString());
|
.Append(_appHost.HttpsPort).Append(Separator)
|
||||||
values.Add((config.EnableRemoteAccess).ToString());
|
.Append(_appHost.EnableHttps).Append(Separator)
|
||||||
|
.Append(config.EnableRemoteAccess).Append(Separator)
|
||||||
return string.Join("|", values.ToArray());
|
.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void _config_ConfigurationUpdated(object sender, EventArgs e)
|
private void OnConfigurationUpdated(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
DisposeNat();
|
Stop();
|
||||||
|
Start();
|
||||||
await RunAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public Task RunAsync()
|
public Task RunAsync()
|
||||||
{
|
{
|
||||||
if (_config.Configuration.EnableUPnP && _config.Configuration.EnableRemoteAccess)
|
Start();
|
||||||
{
|
|
||||||
Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
_config.ConfigurationUpdated -= _config_ConfigurationUpdated;
|
_config.ConfigurationUpdated += OnConfigurationUpdated;
|
||||||
_config.ConfigurationUpdated += _config_ConfigurationUpdated;
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Starting NAT discovery");
|
if (!_config.Configuration.EnableUPnP || !_config.Configuration.EnableRemoteAccess)
|
||||||
if (_natManager == null)
|
|
||||||
{
|
{
|
||||||
_natManager = new NatManager(_logger, _httpClient);
|
return;
|
||||||
_natManager.DeviceFound += NatUtility_DeviceFound;
|
|
||||||
_natManager.StartDiscovery();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("Starting NAT discovery");
|
||||||
|
|
||||||
|
NatUtility.DeviceFound += OnNatUtilityDeviceFound;
|
||||||
|
NatUtility.StartDiscovery();
|
||||||
|
|
||||||
_timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
|
_timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
|
||||||
|
|
||||||
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
|
_deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
|
||||||
|
|
||||||
_lastConfigIdentifier = GetConfigIdentifier();
|
_lastConfigIdentifier = GetConfigIdentifier();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
|
private void Stop()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
_logger.LogDebug("Stopping NAT discovery");
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearCreatedRules(object state)
|
private void ClearCreatedRules(object state)
|
||||||
{
|
{
|
||||||
lock (_createdRules)
|
lock (_createdRulesLock)
|
||||||
{
|
{
|
||||||
_createdRules.Clear();
|
_createdRules.Clear();
|
||||||
}
|
}
|
||||||
lock (_usnsHandled)
|
|
||||||
{
|
|
||||||
_usnsHandled.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NatUtility_DeviceFound(object sender, DeviceEventArgs e)
|
private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
|
||||||
{
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var device = e.Device;
|
var device = e.Device;
|
||||||
|
|
||||||
CreateRules(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)
|
private async void CreateRules(INatDevice device)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
@ -227,15 +151,13 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
|
|
||||||
// On some systems the device discovered event seems to fire repeatedly
|
// 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
|
// 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 (_createdRulesLock)
|
||||||
|
|
||||||
lock (_createdRules)
|
|
||||||
{
|
{
|
||||||
if (!_createdRules.Contains(addressString))
|
if (!_createdRules.Contains(address))
|
||||||
{
|
{
|
||||||
_createdRules.Add(addressString);
|
_createdRules.Add(address);
|
||||||
}
|
}
|
||||||
else
|
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)
|
return device.CreatePortMapAsync(
|
||||||
{
|
new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name));
|
||||||
Description = _appHost.Name
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _disposed = false;
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_disposed = true;
|
Dispose(true);
|
||||||
DisposeNat();
|
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 (_disposed)
|
||||||
|
|
||||||
if (_timer != null)
|
|
||||||
{
|
{
|
||||||
_timer.Dispose();
|
return;
|
||||||
_timer = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
|
_config.ConfigurationUpdated -= OnConfigurationUpdated;
|
||||||
|
|
||||||
var natManager = _natManager;
|
Stop();
|
||||||
|
|
||||||
if (natManager != null)
|
_timer = null;
|
||||||
{
|
|
||||||
_natManager = null;
|
|
||||||
|
|
||||||
using (natManager)
|
_disposed = true;
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
natManager.StopDiscovery();
|
|
||||||
natManager.DeviceFound -= NatUtility_DeviceFound;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error stopping NAT Discovery");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
public class LibraryChangedNotifier : IServerEntryPoint
|
public class LibraryChangedNotifier : IServerEntryPoint
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _library manager
|
/// The library manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _library changed sync lock
|
/// The library changed sync lock.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly object _libraryChangedSyncLock = new object();
|
private readonly object _libraryChangedSyncLock = new object();
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
private Timer LibraryUpdateTimer { get; set; }
|
private Timer LibraryUpdateTimer { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The library update duration
|
/// The library update duration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int LibraryUpdateDuration = 30000;
|
private const int LibraryUpdateDuration = 30000;
|
||||||
|
|
||||||
@ -188,8 +188,11 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
{
|
{
|
||||||
if (LibraryUpdateTimer == null)
|
if (LibraryUpdateTimer == null)
|
||||||
{
|
{
|
||||||
LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
|
LibraryUpdateTimer = new Timer(
|
||||||
Timeout.Infinite);
|
LibraryUpdateTimerCallback,
|
||||||
|
null,
|
||||||
|
LibraryUpdateDuration,
|
||||||
|
Timeout.Infinite);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -452,7 +455,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
return new[] { item };
|
return new[] { item };
|
||||||
}
|
}
|
||||||
|
|
||||||
return new T[] { };
|
return Array.Empty<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _sessionManager.SendMessageToAdminSessions(name, data, CancellationToken.None);
|
await _sessionManager.SendMessageToAdminSessions(name, data, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
@ -325,7 +325,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
|||||||
|
|
||||||
if (options.LogErrorResponseBody)
|
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);
|
_logger.LogError("HTTP request failed with message: {Message}", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ using System.Net.Sockets;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Server.Implementations.Configuration;
|
|
||||||
using Emby.Server.Implementations.Net;
|
using Emby.Server.Implementations.Net;
|
||||||
using Emby.Server.Implementations.Services;
|
using Emby.Server.Implementations.Services;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
@ -16,11 +15,9 @@ using MediaBrowser.Controller;
|
|||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using MediaBrowser.Model.Services;
|
using MediaBrowser.Model.Services;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Internal;
|
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -166,7 +163,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
{
|
{
|
||||||
OnReceive = ProcessWebSocketMessageReceived,
|
OnReceive = ProcessWebSocketMessageReceived,
|
||||||
Url = e.Url,
|
Url = e.Url,
|
||||||
QueryString = e.QueryString ?? new QueryCollection()
|
QueryString = e.QueryString
|
||||||
};
|
};
|
||||||
|
|
||||||
connection.Closed += OnConnectionClosed;
|
connection.Closed += OnConnectionClosed;
|
||||||
@ -539,6 +536,11 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
if (httpRes.StatusCode >= 500)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Sending HTTP Response 500 in response to {Url}", urlToLog);
|
||||||
|
}
|
||||||
|
|
||||||
stopWatch.Stop();
|
stopWatch.Stop();
|
||||||
var elapsed = stopWatch.Elapsed;
|
var elapsed = stopWatch.Elapsed;
|
||||||
if (elapsed.TotalMilliseconds > 500)
|
if (elapsed.TotalMilliseconds > 500)
|
||||||
|
@ -460,7 +460,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(path))
|
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)
|
if (fileShare != FileShareMode.Read && fileShare != FileShareMode.ReadWrite)
|
||||||
|
@ -48,12 +48,14 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
public IDictionary<string, string> Headers => _options;
|
public IDictionary<string, string> Headers => _options;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="StreamWriter" /> class.
|
/// Initializes a new instance of the <see cref="RangeRequestWriter" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rangeHeader">The range header.</param>
|
/// <param name="rangeHeader">The range header.</param>
|
||||||
|
/// <param name="contentLength">The content length.</param>
|
||||||
/// <param name="source">The source.</param>
|
/// <param name="source">The source.</param>
|
||||||
/// <param name="contentType">Type of the content.</param>
|
/// <param name="contentType">Type of the content.</param>
|
||||||
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</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)
|
public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest, ILogger logger)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(contentType))
|
if (string.IsNullOrEmpty(contentType))
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Emby.Server.Implementations.SocketSharp;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
@ -7,22 +8,27 @@ using MediaBrowser.Controller.Net;
|
|||||||
using MediaBrowser.Controller.Security;
|
using MediaBrowser.Controller.Security;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Services;
|
using MediaBrowser.Model.Services;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.HttpServer.Security
|
namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
{
|
{
|
||||||
public class AuthService : IAuthService
|
public class AuthService : IAuthService
|
||||||
{
|
{
|
||||||
|
private readonly ILogger<AuthService> _logger;
|
||||||
private readonly IAuthorizationContext _authorizationContext;
|
private readonly IAuthorizationContext _authorizationContext;
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly INetworkManager _networkManager;
|
private readonly INetworkManager _networkManager;
|
||||||
|
|
||||||
public AuthService(
|
public AuthService(
|
||||||
|
ILogger<AuthService> logger,
|
||||||
IAuthorizationContext authorizationContext,
|
IAuthorizationContext authorizationContext,
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
ISessionManager sessionManager,
|
ISessionManager sessionManager,
|
||||||
INetworkManager networkManager)
|
INetworkManager networkManager)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
_authorizationContext = authorizationContext;
|
_authorizationContext = authorizationContext;
|
||||||
_config = config;
|
_config = config;
|
||||||
_sessionManager = sessionManager;
|
_sessionManager = sessionManager;
|
||||||
@ -34,7 +40,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
ValidateUser(request, authAttribtues);
|
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
|
// This code is executed before the service
|
||||||
var auth = _authorizationContext.GetAuthorizationInfo(request);
|
var auth = _authorizationContext.GetAuthorizationInfo(request);
|
||||||
@ -81,6 +94,8 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
request.RemoteIp,
|
request.RemoteIp,
|
||||||
user);
|
user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateUserAccess(
|
private void ValidateUserAccess(
|
||||||
|
@ -2,11 +2,11 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Common;
|
||||||
using MediaBrowser.Common.Cryptography;
|
using MediaBrowser.Common.Cryptography;
|
||||||
using MediaBrowser.Controller.Authentication;
|
using MediaBrowser.Controller.Authentication;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Model.Cryptography;
|
using MediaBrowser.Model.Cryptography;
|
||||||
using static MediaBrowser.Common.HexHelper;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library
|
namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
@ -59,7 +59,10 @@ namespace Emby.Server.Implementations.Library
|
|||||||
if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)
|
if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)
|
||||||
|| _cryptographyProvider.DefaultHashMethod == 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))
|
if (calculatedHash.SequenceEqual(readyHash.Hash))
|
||||||
{
|
{
|
||||||
@ -122,7 +125,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
return string.IsNullOrEmpty(user.EasyPassword)
|
return string.IsNullOrEmpty(user.EasyPassword)
|
||||||
? null
|
? null
|
||||||
: ToHexString(PasswordHash.Parse(user.EasyPassword).Hash);
|
: Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Authentication;
|
using MediaBrowser.Controller.Authentication;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Net;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library
|
namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
|
@ -519,7 +519,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
|
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(
|
private BaseItem ResolvePath(
|
||||||
FileSystemMetadata fileInfo,
|
FileSystemMetadata fileInfo,
|
||||||
@ -1045,7 +1045,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
await RootFolder.ValidateChildren(
|
await RootFolder.ValidateChildren(
|
||||||
new SimpleProgress<double>(),
|
new SimpleProgress<double>(),
|
||||||
cancellationToken,
|
cancellationToken,
|
||||||
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)),
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
|
||||||
recursive: false).ConfigureAwait(false);
|
recursive: false).ConfigureAwait(false);
|
||||||
|
|
||||||
await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false);
|
await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false);
|
||||||
@ -1053,7 +1053,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
await GetUserRootFolder().ValidateChildren(
|
await GetUserRootFolder().ValidateChildren(
|
||||||
new SimpleProgress<double>(),
|
new SimpleProgress<double>(),
|
||||||
cancellationToken,
|
cancellationToken,
|
||||||
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)),
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
|
||||||
recursive: false).ConfigureAwait(false);
|
recursive: false).ConfigureAwait(false);
|
||||||
|
|
||||||
// Quickly scan CollectionFolders for changes
|
// Quickly scan CollectionFolders for changes
|
||||||
@ -1074,7 +1074,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
innerProgress.RegisterAction(pct => progress.Report(pct * .96));
|
innerProgress.RegisterAction(pct => progress.Report(pct * .96));
|
||||||
|
|
||||||
// Now validate the entire media library
|
// 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);
|
progress.Report(96);
|
||||||
|
|
||||||
@ -1899,7 +1899,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
UpdateItems(new [] { item }, parent, updateReason, cancellationToken);
|
UpdateItems(new[] { item }, parent, updateReason, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -2135,7 +2135,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
if (refresh)
|
if (refresh)
|
||||||
{
|
{
|
||||||
item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
|
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;
|
return item;
|
||||||
@ -2175,7 +2175,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
DisplayParentId = parentId
|
DisplayParentId = parentId
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
CreateItem(item, null);
|
CreateItem(item, null);
|
||||||
|
|
||||||
isNew = true;
|
isNew = true;
|
||||||
@ -2193,11 +2192,10 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
_providerManagerFactory().QueueRefresh(
|
_providerManagerFactory().QueueRefresh(
|
||||||
item.Id,
|
item.Id,
|
||||||
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
{
|
{
|
||||||
// Need to force save to increment DateLastSaved
|
// Need to force save to increment DateLastSaved
|
||||||
ForceSave = true
|
ForceSave = true
|
||||||
|
|
||||||
},
|
},
|
||||||
RefreshPriority.Normal);
|
RefreshPriority.Normal);
|
||||||
}
|
}
|
||||||
@ -2261,7 +2259,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
_providerManagerFactory().QueueRefresh(
|
_providerManagerFactory().QueueRefresh(
|
||||||
item.Id,
|
item.Id,
|
||||||
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
{
|
{
|
||||||
// Need to force save to increment DateLastSaved
|
// Need to force save to increment DateLastSaved
|
||||||
ForceSave = true
|
ForceSave = true
|
||||||
@ -2338,7 +2336,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
_providerManagerFactory().QueueRefresh(
|
_providerManagerFactory().QueueRefresh(
|
||||||
item.Id,
|
item.Id,
|
||||||
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
{
|
{
|
||||||
// Need to force save to increment DateLastSaved
|
// Need to force save to increment DateLastSaved
|
||||||
ForceSave = true
|
ForceSave = true
|
||||||
@ -2487,6 +2485,15 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
episode.ParentIndexNumber = season.IndexNumber;
|
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)
|
if (episode.ParentIndexNumber.HasValue)
|
||||||
{
|
{
|
||||||
|
@ -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))
|
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))
|
await item.RefreshMetadata(
|
||||||
{
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
EnableRemoteContentProbe = true,
|
{
|
||||||
MetadataRefreshMode = MediaBrowser.Controller.Providers.MetadataRefreshMode.FullRefresh
|
EnableRemoteContentProbe = true,
|
||||||
|
MetadataRefreshMode = MetadataRefreshMode.FullRefresh
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
},
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
|
mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(searchTerm))
|
if (string.IsNullOrEmpty(searchTerm))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(searchTerm));
|
throw new ArgumentNullException("SearchTerm can't be empty.", nameof(searchTerm));
|
||||||
}
|
}
|
||||||
|
|
||||||
searchTerm = searchTerm.Trim().RemoveDiacritics();
|
searchTerm = searchTerm.Trim().RemoveDiacritics();
|
||||||
|
@ -24,6 +24,7 @@ using MediaBrowser.Controller.Providers;
|
|||||||
using MediaBrowser.Controller.Security;
|
using MediaBrowser.Controller.Security;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
|
using MediaBrowser.Model.Cryptography;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
@ -31,7 +32,6 @@ using MediaBrowser.Model.IO;
|
|||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using MediaBrowser.Model.Users;
|
using MediaBrowser.Model.Users;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static MediaBrowser.Common.HexHelper;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library
|
namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
@ -60,6 +60,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
private readonly Func<IDtoService> _dtoServiceFactory;
|
private readonly Func<IDtoService> _dtoServiceFactory;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly ICryptoProvider _cryptoProvider;
|
||||||
|
|
||||||
private ConcurrentDictionary<Guid, User> _users;
|
private ConcurrentDictionary<Guid, User> _users;
|
||||||
|
|
||||||
@ -80,7 +81,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
Func<IDtoService> dtoServiceFactory,
|
Func<IDtoService> dtoServiceFactory,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
IJsonSerializer jsonSerializer,
|
IJsonSerializer jsonSerializer,
|
||||||
IFileSystem fileSystem)
|
IFileSystem fileSystem,
|
||||||
|
ICryptoProvider cryptoProvider)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
@ -91,6 +93,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
_cryptoProvider = cryptoProvider;
|
||||||
_users = null;
|
_users = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,12 +182,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
_defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
|
_defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets a User by Id.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The id.</param>
|
|
||||||
/// <returns>User.</returns>
|
|
||||||
/// <exception cref="ArgumentException"></exception>
|
|
||||||
public User GetUserById(Guid id)
|
public User GetUserById(Guid id)
|
||||||
{
|
{
|
||||||
if (id == Guid.Empty)
|
if (id == Guid.Empty)
|
||||||
@ -196,11 +194,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets the user by identifier.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The identifier.</param>
|
|
||||||
/// <returns>User.</returns>
|
|
||||||
public User GetUserById(string id)
|
public User GetUserById(string id)
|
||||||
=> GetUserById(new Guid(id));
|
=> GetUserById(new Guid(id));
|
||||||
|
|
||||||
@ -428,7 +422,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser
|
var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser
|
||||||
? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false)
|
? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false)
|
||||||
: await provider.Authenticate(username, password).ConfigureAwait(false);
|
: await provider.Authenticate(username, password).ConfigureAwait(false);
|
||||||
@ -475,24 +468,21 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (!success
|
if (!success
|
||||||
&& _networkManager.IsInLocalNetwork(remoteEndPoint)
|
&& _networkManager.IsInLocalNetwork(remoteEndPoint)
|
||||||
&& user.Configuration.EnableLocalPassword)
|
&& user.Configuration.EnableLocalPassword
|
||||||
|
&& !string.IsNullOrEmpty(user.EasyPassword))
|
||||||
{
|
{
|
||||||
success = string.Equals(
|
// Check easy password
|
||||||
GetLocalPasswordHash(user),
|
var passwordHash = PasswordHash.Parse(user.EasyPassword);
|
||||||
_defaultAuthenticationProvider.GetHashedString(user, password),
|
var hash = _cryptoProvider.ComputeHash(
|
||||||
StringComparison.OrdinalIgnoreCase);
|
passwordHash.Id,
|
||||||
|
Encoding.UTF8.GetBytes(password),
|
||||||
|
passwordHash.Salt);
|
||||||
|
success = passwordHash.Hash.SequenceEqual(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (authenticationProvider, username, success);
|
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)
|
private void ResetInvalidLoginAttemptCount(User user)
|
||||||
{
|
{
|
||||||
user.Policy.InvalidLoginAttemptCount = 0;
|
user.Policy.InvalidLoginAttemptCount = 0;
|
||||||
@ -538,6 +528,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
defaultName = "MyJellyfinUser";
|
defaultName = "MyJellyfinUser";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_logger.LogWarning("No users, creating one with username {UserName}", defaultName);
|
||||||
|
|
||||||
var name = MakeValidUsername(defaultName);
|
var name = MakeValidUsername(defaultName);
|
||||||
|
|
||||||
var user = InstantiateNewUser(name);
|
var user = InstantiateNewUser(name);
|
||||||
@ -601,7 +593,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
|
// 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)
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -639,7 +631,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
foreach (var user in Users)
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +42,11 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
var user = _userManager.GetUserById(query.UserId);
|
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()
|
var folders = _libraryManager.GetUserRootFolder()
|
||||||
.GetChildren(user, true)
|
.GetChildren(user, true)
|
||||||
.OfType<Folder>()
|
.OfType<Folder>()
|
||||||
@ -54,7 +59,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
foreach (var folder in folders)
|
foreach (var folder in folders)
|
||||||
{
|
{
|
||||||
var collectionFolder = folder as ICollectionFolder;
|
var collectionFolder = folder as ICollectionFolder;
|
||||||
var folderViewType = collectionFolder == null ? null : collectionFolder.CollectionType;
|
var folderViewType = collectionFolder?.CollectionType;
|
||||||
|
|
||||||
if (UserView.IsUserSpecific(folder))
|
if (UserView.IsUserSpecific(folder))
|
||||||
{
|
{
|
||||||
@ -130,16 +135,11 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
var index = orders.IndexOf(i.Id.ToString("N", CultureInfo.InvariantCulture));
|
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;
|
index = orders.IndexOf(view.DisplayParentId.ToString("N", CultureInfo.InvariantCulture));
|
||||||
if (view != null)
|
|
||||||
{
|
|
||||||
if (!view.DisplayParentId.Equals(Guid.Empty))
|
|
||||||
{
|
|
||||||
index = orders.IndexOf(view.DisplayParentId.ToString("N", CultureInfo.InvariantCulture));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return index == -1 ? int.MaxValue : index;
|
return index == -1 ? int.MaxValue : index;
|
||||||
|
@ -28,10 +28,11 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||||||
private readonly IItemRepository _itemRepo;
|
private readonly IItemRepository _itemRepo;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
|
/// Initializes a new instance of the <see cref="ArtistsValidator" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="libraryManager">The library manager.</param>
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="itemRepo">The item repository.</param>
|
||||||
public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||||
{
|
{
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
@ -10,17 +10,18 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||||||
public class GenresPostScanTask : ILibraryPostScanTask
|
public class GenresPostScanTask : ILibraryPostScanTask
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _library manager
|
/// The _library manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IItemRepository _itemRepo;
|
private readonly IItemRepository _itemRepo;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
|
/// Initializes a new instance of the <see cref="GenresPostScanTask" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="libraryManager">The library manager.</param>
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="itemRepo">The item repository.</param>
|
||||||
public GenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
public GenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||||
{
|
{
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
@ -20,10 +20,11 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||||||
private readonly IItemRepository _itemRepo;
|
private readonly IItemRepository _itemRepo;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
|
/// Initializes a new instance of the <see cref="MusicGenresPostScanTask" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="libraryManager">The library manager.</param>
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="itemRepo">The item repository.</param>
|
||||||
public MusicGenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
public MusicGenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||||
{
|
{
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
@ -11,16 +11,17 @@ using Microsoft.Extensions.Logging;
|
|||||||
namespace Emby.Server.Implementations.Library.Validators
|
namespace Emby.Server.Implementations.Library.Validators
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class PeopleValidator
|
/// Class PeopleValidator.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PeopleValidator
|
public class PeopleValidator
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _library manager
|
/// The _library manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _logger
|
/// The _logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
@ -62,7 +63,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||||||
{
|
{
|
||||||
var item = _libraryManager.GetPerson(person);
|
var item = _libraryManager.GetPerson(person);
|
||||||
|
|
||||||
var options = new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
{
|
{
|
||||||
ImageRefreshMode = MetadataRefreshMode.ValidationOnly,
|
ImageRefreshMode = MetadataRefreshMode.ValidationOnly,
|
||||||
MetadataRefreshMode = MetadataRefreshMode.ValidationOnly
|
MetadataRefreshMode = MetadataRefreshMode.ValidationOnly
|
||||||
@ -96,12 +97,19 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||||||
|
|
||||||
foreach (var item in deadEntities)
|
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
|
_libraryManager.DeleteItem(
|
||||||
{
|
item,
|
||||||
DeleteFileLocation = false
|
new DeleteOptions
|
||||||
}, false);
|
{
|
||||||
|
DeleteFileLocation = false
|
||||||
|
},
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
progress.Report(100);
|
progress.Report(100);
|
||||||
|
@ -21,9 +21,11 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||||||
private readonly IItemRepository _itemRepo;
|
private readonly IItemRepository _itemRepo;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
|
/// Initializes a new instance of the <see cref="StudiosPostScanTask" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="libraryManager">The library manager.</param>
|
/// <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)
|
public StudiosPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||||
{
|
{
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
if (requiresRefresh)
|
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);
|
_logger.LogInformation("Refreshing recording parent {path}", item.Path);
|
||||||
|
|
||||||
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
_providerManager.QueueRefresh(
|
||||||
{
|
item.Id,
|
||||||
RefreshPaths = new string[]
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
{
|
{
|
||||||
path,
|
RefreshPaths = new string[]
|
||||||
Path.GetDirectoryName(path),
|
{
|
||||||
Path.GetDirectoryName(Path.GetDirectoryName(path))
|
path,
|
||||||
}
|
Path.GetDirectoryName(path),
|
||||||
|
Path.GetDirectoryName(Path.GetDirectoryName(path))
|
||||||
}, RefreshPriority.High);
|
}
|
||||||
|
},
|
||||||
|
RefreshPriority.High);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -501,7 +501,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
|
|
||||||
public async Task<List<NameIdPair>> GetHeadends(ListingsProviderInfo info, string country, string location, CancellationToken cancellationToken)
|
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>();
|
var lineups = new List<NameIdPair>();
|
||||||
|
|
||||||
@ -713,7 +713,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
|
|
||||||
private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken)
|
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))
|
if (string.IsNullOrEmpty(token))
|
||||||
{
|
{
|
||||||
@ -738,7 +738,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
|
|
||||||
httpOptions.RequestHeaders["token"] = token;
|
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");
|
throw new ArgumentException("Listings Id required");
|
||||||
}
|
}
|
||||||
|
|
||||||
var token = await GetToken(info, cancellationToken);
|
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(token))
|
if (string.IsNullOrEmpty(token))
|
||||||
{
|
{
|
||||||
@ -833,7 +833,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
throw new Exception("ListingsId required");
|
throw new Exception("ListingsId required");
|
||||||
}
|
}
|
||||||
|
|
||||||
var token = await GetToken(info, cancellationToken);
|
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(token))
|
if (string.IsNullOrEmpty(token))
|
||||||
{
|
{
|
||||||
|
@ -1226,12 +1226,13 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
currentChannel.AddTag("Kids");
|
currentChannel.AddTag("Kids");
|
||||||
}
|
}
|
||||||
|
|
||||||
//currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken);
|
currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken);
|
||||||
await currentChannel.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
await currentChannel.RefreshMetadata(
|
||||||
{
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
ForceSave = true
|
{
|
||||||
|
ForceSave = true
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
},
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@ -1245,7 +1246,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
numComplete++;
|
numComplete++;
|
||||||
double percent = numComplete / (double)allChannelsList.Count;
|
double percent = numComplete / (double)allChannelsList.Count;
|
||||||
|
|
||||||
progress.Report(85 * percent + 15);
|
progress.Report((85 * percent) + 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
progress.Report(100);
|
progress.Report(100);
|
||||||
@ -1278,12 +1279,14 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
|
|
||||||
if (item != null)
|
if (item != null)
|
||||||
{
|
{
|
||||||
_libraryManager.DeleteItem(item, new DeleteOptions
|
_libraryManager.DeleteItem(
|
||||||
{
|
item,
|
||||||
DeleteFileLocation = false,
|
new DeleteOptions
|
||||||
DeleteFromExternalProvider = false
|
{
|
||||||
|
DeleteFileLocation = false,
|
||||||
}, false);
|
DeleteFromExternalProvider = false
|
||||||
|
},
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2301,8 +2304,10 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
if (provider == null)
|
if (provider == null)
|
||||||
{
|
{
|
||||||
throw new ResourceNotFoundException(
|
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);
|
await provider.Validate(info, validateLogin, validateListings).ConfigureAwait(false);
|
||||||
|
@ -38,8 +38,8 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||||
{
|
{
|
||||||
return new[] {
|
return new[]
|
||||||
|
{
|
||||||
// Every so often
|
// Every so often
|
||||||
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
||||||
};
|
};
|
||||||
|
@ -185,7 +185,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||||||
Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
|
Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
|
||||||
CancellationToken = cancellationToken,
|
CancellationToken = cancellationToken,
|
||||||
BufferContent = false
|
BufferContent = false
|
||||||
}, HttpMethod.Get))
|
}, HttpMethod.Get).ConfigureAwait(false))
|
||||||
using (var stream = response.Content)
|
using (var stream = response.Content)
|
||||||
using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
|
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)
|
for (int i = 0; i < model.TunerCount; ++i)
|
||||||
{
|
{
|
||||||
var name = string.Format("Tuner {0}", i + 1);
|
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 isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false);
|
||||||
var status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv;
|
var status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv;
|
||||||
tuners.Add(new LiveTvTunerInfo
|
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)
|
public async Task<List<LiveTvTunerInfo>> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// TODO Need faster way to determine UDP vs HTTP
|
// 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;
|
var hdHomerunChannelInfo = channels.FirstOrDefault() as HdHomerunChannelInfo;
|
||||||
|
|
||||||
@ -582,11 +582,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||||||
modelInfo.TunerCount,
|
modelInfo.TunerCount,
|
||||||
FileSystem,
|
FileSystem,
|
||||||
Logger,
|
Logger,
|
||||||
Config.ApplicationPaths,
|
Config,
|
||||||
_appHost,
|
_appHost,
|
||||||
_networkManager,
|
_networkManager,
|
||||||
_streamHelper);
|
_streamHelper);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var enableHttpStream = true;
|
var enableHttpStream = true;
|
||||||
@ -611,7 +610,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||||||
FileSystem,
|
FileSystem,
|
||||||
_httpClient,
|
_httpClient,
|
||||||
Logger,
|
Logger,
|
||||||
Config.ApplicationPaths,
|
Config,
|
||||||
_appHost,
|
_appHost,
|
||||||
_streamHelper);
|
_streamHelper);
|
||||||
}
|
}
|
||||||
@ -624,7 +623,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||||||
modelInfo.TunerCount,
|
modelInfo.TunerCount,
|
||||||
FileSystem,
|
FileSystem,
|
||||||
Logger,
|
Logger,
|
||||||
Config.ApplicationPaths,
|
Config,
|
||||||
_appHost,
|
_appHost,
|
||||||
_networkManager,
|
_networkManager,
|
||||||
_streamHelper);
|
_streamHelper);
|
||||||
|
@ -6,6 +6,7 @@ using System.Net.Sockets;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
@ -33,11 +34,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||||||
int numTuners,
|
int numTuners,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
IServerApplicationPaths appPaths,
|
IConfigurationManager configurationManager,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
INetworkManager networkManager,
|
INetworkManager networkManager,
|
||||||
IStreamHelper streamHelper)
|
IStreamHelper streamHelper)
|
||||||
: base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
|
: base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
|
||||||
{
|
{
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_networkManager = networkManager;
|
_networkManager = networkManager;
|
||||||
|
@ -5,8 +5,8 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.LiveTv;
|
using MediaBrowser.Model.LiveTv;
|
||||||
@ -16,8 +16,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||||||
{
|
{
|
||||||
public class LiveStream : ILiveStream
|
public class LiveStream : ILiveStream
|
||||||
{
|
{
|
||||||
|
private readonly IConfigurationManager _configurationManager;
|
||||||
|
|
||||||
protected readonly IFileSystem FileSystem;
|
protected readonly IFileSystem FileSystem;
|
||||||
protected readonly IServerApplicationPaths AppPaths;
|
|
||||||
protected readonly IStreamHelper StreamHelper;
|
protected readonly IStreamHelper StreamHelper;
|
||||||
|
|
||||||
protected string TempFilePath;
|
protected string TempFilePath;
|
||||||
@ -29,7 +31,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||||||
TunerHostInfo tuner,
|
TunerHostInfo tuner,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
IServerApplicationPaths appPaths,
|
IConfigurationManager configurationManager,
|
||||||
IStreamHelper streamHelper)
|
IStreamHelper streamHelper)
|
||||||
{
|
{
|
||||||
OriginalMediaSource = mediaSource;
|
OriginalMediaSource = mediaSource;
|
||||||
@ -44,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||||||
TunerHostId = tuner.Id;
|
TunerHostId = tuner.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
AppPaths = appPaths;
|
_configurationManager = configurationManager;
|
||||||
StreamHelper = streamHelper;
|
StreamHelper = streamHelper;
|
||||||
|
|
||||||
ConsumerCount = 1;
|
ConsumerCount = 1;
|
||||||
@ -68,7 +70,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||||||
|
|
||||||
protected void SetTempFilePath(string extension)
|
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)
|
public virtual Task Open(CancellationToken openCancellationToken)
|
||||||
|
@ -114,11 +114,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||||||
|
|
||||||
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
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)
|
public async Task Validate(TunerHostInfo info)
|
||||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
@ -26,10 +27,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IHttpClient httpClient,
|
IHttpClient httpClient,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
IServerApplicationPaths appPaths,
|
IConfigurationManager configurationManager,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
IStreamHelper streamHelper)
|
IStreamHelper streamHelper)
|
||||||
: base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
|
: base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
|
96
Emby.Server.Implementations/Localization/Core/af.json
Normal file
96
Emby.Server.Implementations/Localization/Core/af.json
Normal 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"
|
||||||
|
}
|
@ -1,22 +1,22 @@
|
|||||||
{
|
{
|
||||||
"Albums": "Албуми",
|
"Albums": "Албуми",
|
||||||
"AppDeviceValues": "Програма: {0}, Устройство: {1}",
|
"AppDeviceValues": "Програма: {0}, устройство: {1}",
|
||||||
"Application": "Програма",
|
"Application": "Програма",
|
||||||
"Artists": "Изпълнители",
|
"Artists": "Изпълнители",
|
||||||
"AuthenticationSucceededWithUserName": "{0} се удостовери успешно",
|
"AuthenticationSucceededWithUserName": "{0} се удостовери успешно",
|
||||||
"Books": "Книги",
|
"Books": "Книги",
|
||||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
"CameraImageUploadedFrom": "",
|
||||||
"Channels": "Канали",
|
"Channels": "Канали",
|
||||||
"ChapterNameValue": "Глава {0}",
|
"ChapterNameValue": "Глава {0}",
|
||||||
"Collections": "Колекции",
|
"Collections": "Колекции",
|
||||||
"DeviceOfflineWithName": "{0} се разкачи",
|
"DeviceOfflineWithName": "{0} се разкачи",
|
||||||
"DeviceOnlineWithName": "{0} е свързан",
|
"DeviceOnlineWithName": "{0} е свързан",
|
||||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
"FailedLoginAttemptWithUserName": "",
|
||||||
"Favorites": "Любими",
|
"Favorites": "Любими",
|
||||||
"Folders": "Папки",
|
"Folders": "Папки",
|
||||||
"Genres": "Жанрове",
|
"Genres": "Жанрове",
|
||||||
"HeaderAlbumArtists": "Изпълнители на албуми",
|
"HeaderAlbumArtists": "Изпълнители на албуми",
|
||||||
"HeaderCameraUploads": "Camera Uploads",
|
"HeaderCameraUploads": "",
|
||||||
"HeaderContinueWatching": "Продължаване на гледането",
|
"HeaderContinueWatching": "Продължаване на гледането",
|
||||||
"HeaderFavoriteAlbums": "Любими албуми",
|
"HeaderFavoriteAlbums": "Любими албуми",
|
||||||
"HeaderFavoriteArtists": "Любими изпълнители",
|
"HeaderFavoriteArtists": "Любими изпълнители",
|
||||||
@ -25,26 +25,26 @@
|
|||||||
"HeaderFavoriteSongs": "Любими песни",
|
"HeaderFavoriteSongs": "Любими песни",
|
||||||
"HeaderLiveTV": "Телевизия на живо",
|
"HeaderLiveTV": "Телевизия на живо",
|
||||||
"HeaderNextUp": "Следва",
|
"HeaderNextUp": "Следва",
|
||||||
"HeaderRecordingGroups": "Recording Groups",
|
"HeaderRecordingGroups": "",
|
||||||
"HomeVideos": "Домашни клипове",
|
"HomeVideos": "Домашни клипове",
|
||||||
"Inherit": "Наследяване",
|
"Inherit": "Наследяване",
|
||||||
"ItemAddedWithName": "{0} е добавено към библиотеката",
|
"ItemAddedWithName": "{0} е добавено към библиотеката",
|
||||||
"ItemRemovedWithName": "{0} е премахнато от библиотеката",
|
"ItemRemovedWithName": "{0} е премахнато от библиотеката",
|
||||||
"LabelIpAddressValue": "ИП адрес: {0}",
|
"LabelIpAddressValue": "ИП адрес: {0}",
|
||||||
"LabelRunningTimeValue": "Running time: {0}",
|
"LabelRunningTimeValue": "",
|
||||||
"Latest": "Последни",
|
"Latest": "Последни",
|
||||||
"MessageApplicationUpdated": "Сървърът е обновен",
|
"MessageApplicationUpdated": "Сървърът е обновен",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
"MessageApplicationUpdatedTo": "",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
|
"MessageNamedServerConfigurationUpdatedWithValue": "",
|
||||||
"MessageServerConfigurationUpdated": "Server configuration has been updated",
|
"MessageServerConfigurationUpdated": "",
|
||||||
"MixedContent": "Смесено съдържание",
|
"MixedContent": "Смесено съдържание",
|
||||||
"Movies": "Филми",
|
"Movies": "Филми",
|
||||||
"Music": "Музика",
|
"Music": "Музика",
|
||||||
"MusicVideos": "Музикални клипове",
|
"MusicVideos": "Музикални клипове",
|
||||||
"NameInstallFailed": "{0} installation failed",
|
"NameInstallFailed": "",
|
||||||
"NameSeasonNumber": "Сезон {0}",
|
"NameSeasonNumber": "Сезон {0}",
|
||||||
"NameSeasonUnknown": "Season Unknown",
|
"NameSeasonUnknown": "Неразпознат сезон",
|
||||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
"NewVersionIsAvailable": "",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "Налично е обновление на програмата",
|
"NotificationOptionApplicationUpdateAvailable": "Налично е обновление на програмата",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Обновлението на програмата е инсталирано",
|
"NotificationOptionApplicationUpdateInstalled": "Обновлението на програмата е инсталирано",
|
||||||
"NotificationOptionAudioPlayback": "Възпроизвеждането на звук започна",
|
"NotificationOptionAudioPlayback": "Възпроизвеждането на звук започна",
|
||||||
@ -58,7 +58,7 @@
|
|||||||
"NotificationOptionPluginUpdateInstalled": "Обновлението на приставката е инсталирано",
|
"NotificationOptionPluginUpdateInstalled": "Обновлението на приставката е инсталирано",
|
||||||
"NotificationOptionServerRestartRequired": "Нужно е повторно пускане на сървъра",
|
"NotificationOptionServerRestartRequired": "Нужно е повторно пускане на сървъра",
|
||||||
"NotificationOptionTaskFailed": "Грешка в планирана задача",
|
"NotificationOptionTaskFailed": "Грешка в планирана задача",
|
||||||
"NotificationOptionUserLockedOut": "User locked out",
|
"NotificationOptionUserLockedOut": "",
|
||||||
"NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна",
|
"NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна",
|
||||||
"NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно",
|
"NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно",
|
||||||
"Photos": "Снимки",
|
"Photos": "Снимки",
|
||||||
@ -70,12 +70,12 @@
|
|||||||
"ProviderValue": "Доставчик: {0}",
|
"ProviderValue": "Доставчик: {0}",
|
||||||
"ScheduledTaskFailedWithName": "{0} се провали",
|
"ScheduledTaskFailedWithName": "{0} се провали",
|
||||||
"ScheduledTaskStartedWithName": "{0} започна",
|
"ScheduledTaskStartedWithName": "{0} започна",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
"ServerNameNeedsToBeRestarted": "",
|
||||||
"Shows": "Сериали",
|
"Shows": "Сериали",
|
||||||
"Songs": "Песни",
|
"Songs": "Песни",
|
||||||
"StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.",
|
"StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.",
|
||||||
"SubtitleDownloadFailureForItem": "Неуспешно изтегляне на субтитри за {0}",
|
"SubtitleDownloadFailureForItem": "Неуспешно изтегляне на субтитри за {0}",
|
||||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
"SubtitleDownloadFailureFromForItem": "",
|
||||||
"SubtitlesDownloadedForItem": "Изтеглени са субтитри за {0}",
|
"SubtitlesDownloadedForItem": "Изтеглени са субтитри за {0}",
|
||||||
"Sync": "Синхронизиране",
|
"Sync": "Синхронизиране",
|
||||||
"System": "Система",
|
"System": "Система",
|
||||||
@ -83,15 +83,15 @@
|
|||||||
"User": "Потребител",
|
"User": "Потребител",
|
||||||
"UserCreatedWithName": "Потребителят {0} е създаден",
|
"UserCreatedWithName": "Потребителят {0} е създаден",
|
||||||
"UserDeletedWithName": "Потребителят {0} е изтрит",
|
"UserDeletedWithName": "Потребителят {0} е изтрит",
|
||||||
"UserDownloadingItemWithValues": "{0} is downloading {1}",
|
"UserDownloadingItemWithValues": "",
|
||||||
"UserLockedOutWithName": "User {0} has been locked out",
|
"UserLockedOutWithName": "",
|
||||||
"UserOfflineFromDevice": "{0} се разкачи от {1}",
|
"UserOfflineFromDevice": "{0} се разкачи от {1}",
|
||||||
"UserOnlineFromDevice": "{0} е на линия от {1}",
|
"UserOnlineFromDevice": "{0} е на линия от {1}",
|
||||||
"UserPasswordChangedWithName": "Паролата на потребителя {0} е променена",
|
"UserPasswordChangedWithName": "Паролата на потребителя {0} е променена",
|
||||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
"UserPolicyUpdatedWithName": "",
|
||||||
"UserStartedPlayingItemWithValues": "{0} пусна {1}",
|
"UserStartedPlayingItemWithValues": "{0} пусна {1}",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} спря {1}",
|
"UserStoppedPlayingItemWithValues": "{0} спря {1}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
"ValueHasBeenAddedToLibrary": "",
|
||||||
"ValueSpecialEpisodeName": "Специални - {0}",
|
"ValueSpecialEpisodeName": "Специални - {0}",
|
||||||
"VersionNumber": "Версия {0}"
|
"VersionNumber": "Версия {0}"
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"HeaderFavoriteEpisodes": "Oblíbené epizody",
|
"HeaderFavoriteEpisodes": "Oblíbené epizody",
|
||||||
"HeaderFavoriteShows": "Oblíbené seriály",
|
"HeaderFavoriteShows": "Oblíbené seriály",
|
||||||
"HeaderFavoriteSongs": "Oblíbená hudba",
|
"HeaderFavoriteSongs": "Oblíbená hudba",
|
||||||
"HeaderLiveTV": "Live TV",
|
"HeaderLiveTV": "Živá TV",
|
||||||
"HeaderNextUp": "Nadcházející",
|
"HeaderNextUp": "Nadcházející",
|
||||||
"HeaderRecordingGroups": "Skupiny nahrávek",
|
"HeaderRecordingGroups": "Skupiny nahrávek",
|
||||||
"HomeVideos": "Domáci videa",
|
"HomeVideos": "Domáci videa",
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
"AppDeviceValues": "App: {0}, Gerät: {1}",
|
"AppDeviceValues": "App: {0}, Gerät: {1}",
|
||||||
"Application": "Anwendung",
|
"Application": "Anwendung",
|
||||||
"Artists": "Interpreten",
|
"Artists": "Interpreten",
|
||||||
"AuthenticationSucceededWithUserName": "{0} hat sich angemeldet",
|
"AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich angemeldet",
|
||||||
"Books": "Bücher",
|
"Books": "Bücher",
|
||||||
"CameraImageUploadedFrom": "Ein neues Foto wurde hochgeladen von {0}",
|
"CameraImageUploadedFrom": "Ein neues Foto wurde hochgeladen von {0}",
|
||||||
"Channels": "Kanäle",
|
"Channels": "Kanäle",
|
||||||
"ChapterNameValue": "Kapitel {0}",
|
"ChapterNameValue": "Kapitel {0}",
|
||||||
"Collections": "Sammlungen",
|
"Collections": "Sammlungen",
|
||||||
"DeviceOfflineWithName": "{0} wurde getrennt",
|
"DeviceOfflineWithName": "{0} wurde getrennt",
|
||||||
"DeviceOnlineWithName": "{0} hat sich verbunden",
|
"DeviceOnlineWithName": "{0} ist verbunden",
|
||||||
"FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
|
"FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
|
||||||
"Favorites": "Favoriten",
|
"Favorites": "Favoriten",
|
||||||
"Folders": "Verzeichnisse",
|
"Folders": "Verzeichnisse",
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"HeaderFavoriteEpisodes": "Lieblingsepisoden",
|
"HeaderFavoriteEpisodes": "Lieblingsepisoden",
|
||||||
"HeaderFavoriteShows": "Lieblingsserien",
|
"HeaderFavoriteShows": "Lieblingsserien",
|
||||||
"HeaderFavoriteSongs": "Lieblingslieder",
|
"HeaderFavoriteSongs": "Lieblingslieder",
|
||||||
"HeaderLiveTV": "Live-TV",
|
"HeaderLiveTV": "Live TV",
|
||||||
"HeaderNextUp": "Als Nächstes",
|
"HeaderNextUp": "Als Nächstes",
|
||||||
"HeaderRecordingGroups": "Aufnahme-Gruppen",
|
"HeaderRecordingGroups": "Aufnahme-Gruppen",
|
||||||
"HomeVideos": "Heimvideos",
|
"HomeVideos": "Heimvideos",
|
||||||
@ -35,7 +35,7 @@
|
|||||||
"Latest": "Neueste",
|
"Latest": "Neueste",
|
||||||
"MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert",
|
"MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert",
|
"MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "Der Server Einstellungsbereich {0} wurde aktualisiert",
|
"MessageNamedServerConfigurationUpdatedWithValue": "Der Server-Einstellungsbereich {0} wurde aktualisiert",
|
||||||
"MessageServerConfigurationUpdated": "Servereinstellungen wurden aktualisiert",
|
"MessageServerConfigurationUpdated": "Servereinstellungen wurden aktualisiert",
|
||||||
"MixedContent": "Gemischte Inhalte",
|
"MixedContent": "Gemischte Inhalte",
|
||||||
"Movies": "Filme",
|
"Movies": "Filme",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"Artists": "Artistes",
|
"Artists": "Artistes",
|
||||||
"AuthenticationSucceededWithUserName": "{0} s'est authentifié avec succès",
|
"AuthenticationSucceededWithUserName": "{0} s'est authentifié avec succès",
|
||||||
"Books": "Livres",
|
"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",
|
"Channels": "Chaînes",
|
||||||
"ChapterNameValue": "Chapitre {0}",
|
"ChapterNameValue": "Chapitre {0}",
|
||||||
"Collections": "Collections",
|
"Collections": "Collections",
|
||||||
@ -17,7 +17,7 @@
|
|||||||
"Genres": "Genres",
|
"Genres": "Genres",
|
||||||
"HeaderAlbumArtists": "Artistes de l'album",
|
"HeaderAlbumArtists": "Artistes de l'album",
|
||||||
"HeaderCameraUploads": "Photos transférées",
|
"HeaderCameraUploads": "Photos transférées",
|
||||||
"HeaderContinueWatching": "Reprendre",
|
"HeaderContinueWatching": "Continuer à regarder",
|
||||||
"HeaderFavoriteAlbums": "Albums favoris",
|
"HeaderFavoriteAlbums": "Albums favoris",
|
||||||
"HeaderFavoriteArtists": "Artistes favoris",
|
"HeaderFavoriteArtists": "Artistes favoris",
|
||||||
"HeaderFavoriteEpisodes": "Épisodes favoris",
|
"HeaderFavoriteEpisodes": "Épisodes favoris",
|
||||||
@ -34,14 +34,14 @@
|
|||||||
"LabelRunningTimeValue": "Durée : {0}",
|
"LabelRunningTimeValue": "Durée : {0}",
|
||||||
"Latest": "Derniers",
|
"Latest": "Derniers",
|
||||||
"MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour",
|
"MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Serveur a été mis à jour en version {0}",
|
"MessageApplicationUpdatedTo": "Le serveur Jellyfin a été mis à jour vers {0}",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour",
|
"MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour",
|
||||||
"MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour",
|
"MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour",
|
||||||
"MixedContent": "Contenu mixte",
|
"MixedContent": "Contenu mixte",
|
||||||
"Movies": "Films",
|
"Movies": "Films",
|
||||||
"Music": "Musique",
|
"Music": "Musique",
|
||||||
"MusicVideos": "Vidéos musicales",
|
"MusicVideos": "Vidéos musicales",
|
||||||
"NameInstallFailed": "{0} échec d'installation",
|
"NameInstallFailed": "{0} échec de l'installation",
|
||||||
"NameSeasonNumber": "Saison {0}",
|
"NameSeasonNumber": "Saison {0}",
|
||||||
"NameSeasonUnknown": "Saison Inconnue",
|
"NameSeasonUnknown": "Saison Inconnue",
|
||||||
"NewVersionIsAvailable": "Une nouvelle version de Jellyfin Serveur est disponible au téléchargement.",
|
"NewVersionIsAvailable": "Une nouvelle version de Jellyfin Serveur est disponible au téléchargement.",
|
||||||
@ -50,7 +50,7 @@
|
|||||||
"NotificationOptionAudioPlayback": "Lecture audio démarrée",
|
"NotificationOptionAudioPlayback": "Lecture audio démarrée",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Lecture audio arrêtée",
|
"NotificationOptionAudioPlaybackStopped": "Lecture audio arrêtée",
|
||||||
"NotificationOptionCameraImageUploaded": "L'image de l'appareil photo a été transféré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é",
|
"NotificationOptionNewLibraryContent": "Nouveau contenu ajouté",
|
||||||
"NotificationOptionPluginError": "Erreur d'extension",
|
"NotificationOptionPluginError": "Erreur d'extension",
|
||||||
"NotificationOptionPluginInstalled": "Extension installée",
|
"NotificationOptionPluginInstalled": "Extension installée",
|
||||||
@ -91,7 +91,7 @@
|
|||||||
"UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}",
|
"UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}",
|
||||||
"UserStartedPlayingItemWithValues": "{0} est en train de lire {1} sur {2}",
|
"UserStartedPlayingItemWithValues": "{0} est en train de lire {1} sur {2}",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {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}",
|
"ValueSpecialEpisodeName": "Spécial - {0}",
|
||||||
"VersionNumber": "Version {0}"
|
"VersionNumber": "Version {0}"
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,41 @@
|
|||||||
{
|
{
|
||||||
"Albums": "אלבומים",
|
"Albums": "אלבומים",
|
||||||
"AppDeviceValues": "App: {0}, Device: {1}",
|
"AppDeviceValues": "יישום: {0}, מכשיר: {1}",
|
||||||
"Application": "אפליקציה",
|
"Application": "אפליקציה",
|
||||||
"Artists": "אמנים",
|
"Artists": "אמנים",
|
||||||
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
"AuthenticationSucceededWithUserName": "{0} זוהה בהצלחה",
|
||||||
"Books": "ספרים",
|
"Books": "ספרים",
|
||||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
"CameraImageUploadedFrom": "תמונה חדשה הועלתה מ{0}",
|
||||||
"Channels": "Channels",
|
"Channels": "ערוצים",
|
||||||
"ChapterNameValue": "Chapter {0}",
|
"ChapterNameValue": "פרק {0}",
|
||||||
"Collections": "Collections",
|
"Collections": "קולקציות",
|
||||||
"DeviceOfflineWithName": "{0} has disconnected",
|
"DeviceOfflineWithName": "{0} התנתק",
|
||||||
"DeviceOnlineWithName": "{0} is connected",
|
"DeviceOnlineWithName": "{0} מחובר",
|
||||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
"FailedLoginAttemptWithUserName": "ניסיון כניסה שגוי מ{0}",
|
||||||
"Favorites": "Favorites",
|
"Favorites": "אהובים",
|
||||||
"Folders": "Folders",
|
"Folders": "תיקיות",
|
||||||
"Genres": "ז'אנרים",
|
"Genres": "ז'אנרים",
|
||||||
"HeaderAlbumArtists": "Album Artists",
|
"HeaderAlbumArtists": "אמני האלבום",
|
||||||
"HeaderCameraUploads": "Camera Uploads",
|
"HeaderCameraUploads": "העלאות ממצלמה",
|
||||||
"HeaderContinueWatching": "המשך בצפייה",
|
"HeaderContinueWatching": "המשך לצפות",
|
||||||
"HeaderFavoriteAlbums": "Favorite Albums",
|
"HeaderFavoriteAlbums": "אלבומים שאהבתי",
|
||||||
"HeaderFavoriteArtists": "Favorite Artists",
|
"HeaderFavoriteArtists": "אמנים שאהבתי",
|
||||||
"HeaderFavoriteEpisodes": "Favorite Episodes",
|
"HeaderFavoriteEpisodes": "פרקים אהובים",
|
||||||
"HeaderFavoriteShows": "Favorite Shows",
|
"HeaderFavoriteShows": "תוכניות אהובות",
|
||||||
"HeaderFavoriteSongs": "Favorite Songs",
|
"HeaderFavoriteSongs": "שירים שאהבתי",
|
||||||
"HeaderLiveTV": "Live TV",
|
"HeaderLiveTV": "טלוויזיה בשידור חי",
|
||||||
"HeaderNextUp": "Next Up",
|
"HeaderNextUp": "הבא",
|
||||||
"HeaderRecordingGroups": "קבוצות הקלטה",
|
"HeaderRecordingGroups": "קבוצות הקלטה",
|
||||||
"HomeVideos": "Home videos",
|
"HomeVideos": "סרטונים בייתים",
|
||||||
"Inherit": "Inherit",
|
"Inherit": "הורש",
|
||||||
"ItemAddedWithName": "{0} was added to the library",
|
"ItemAddedWithName": "{0} was added to the library",
|
||||||
"ItemRemovedWithName": "{0} was removed from the library",
|
"ItemRemovedWithName": "{0} נמחק מהספרייה",
|
||||||
"LabelIpAddressValue": "Ip address: {0}",
|
"LabelIpAddressValue": "Ip כתובת: {0}",
|
||||||
"LabelRunningTimeValue": "Running time: {0}",
|
"LabelRunningTimeValue": "משך צפייה: {0}",
|
||||||
"Latest": "אחרון",
|
"Latest": "אחרון",
|
||||||
"MessageApplicationUpdated": "Jellyfin Server has been updated",
|
"MessageApplicationUpdated": "שרת הJellyfin עודכן",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
"MessageApplicationUpdatedTo": "שרת הJellyfin עודכן לגרסא {0}",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
|
"MessageNamedServerConfigurationUpdatedWithValue": "הגדרת השרת {0} שונתה",
|
||||||
"MessageServerConfigurationUpdated": "Server configuration has been updated",
|
"MessageServerConfigurationUpdated": "Server configuration has been updated",
|
||||||
"MixedContent": "תוכן מעורב",
|
"MixedContent": "תוכן מעורב",
|
||||||
"Movies": "סרטים",
|
"Movies": "סרטים",
|
||||||
@ -50,7 +50,7 @@
|
|||||||
"NotificationOptionAudioPlayback": "Audio playback started",
|
"NotificationOptionAudioPlayback": "Audio playback started",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
||||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||||
"NotificationOptionInstallationFailed": "Installation failure",
|
"NotificationOptionInstallationFailed": "התקנה נכשלה",
|
||||||
"NotificationOptionNewLibraryContent": "New content added",
|
"NotificationOptionNewLibraryContent": "New content added",
|
||||||
"NotificationOptionPluginError": "Plugin failure",
|
"NotificationOptionPluginError": "Plugin failure",
|
||||||
"NotificationOptionPluginInstalled": "Plugin installed",
|
"NotificationOptionPluginInstalled": "Plugin installed",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"Artists": "Előadók",
|
"Artists": "Előadók",
|
||||||
"AuthenticationSucceededWithUserName": "{0} sikeresen azonosítva",
|
"AuthenticationSucceededWithUserName": "{0} sikeresen azonosítva",
|
||||||
"Books": "Könyvek",
|
"Books": "Könyvek",
|
||||||
"CameraImageUploadedFrom": "Új kamerakép került feltöltésre {0}",
|
"CameraImageUploadedFrom": "Új kamerakép került feltöltésre innen: {0}",
|
||||||
"Channels": "Csatornák",
|
"Channels": "Csatornák",
|
||||||
"ChapterNameValue": "Jelenet {0}",
|
"ChapterNameValue": "Jelenet {0}",
|
||||||
"Collections": "Gyűjtemények",
|
"Collections": "Gyűjtemények",
|
||||||
@ -15,14 +15,14 @@
|
|||||||
"Favorites": "Kedvencek",
|
"Favorites": "Kedvencek",
|
||||||
"Folders": "Könyvtárak",
|
"Folders": "Könyvtárak",
|
||||||
"Genres": "Műfajok",
|
"Genres": "Műfajok",
|
||||||
"HeaderAlbumArtists": "Album Előadók",
|
"HeaderAlbumArtists": "Album előadók",
|
||||||
"HeaderCameraUploads": "Kamera feltöltések",
|
"HeaderCameraUploads": "Kamera feltöltések",
|
||||||
"HeaderContinueWatching": "Folyamatban lévő filmek",
|
"HeaderContinueWatching": "Folyamatban lévő filmek",
|
||||||
"HeaderFavoriteAlbums": "Kedvenc Albumok",
|
"HeaderFavoriteAlbums": "Kedvenc albumok",
|
||||||
"HeaderFavoriteArtists": "Kedvenc Előadók",
|
"HeaderFavoriteArtists": "Kedvenc előadók",
|
||||||
"HeaderFavoriteEpisodes": "Kedvenc Epizódok",
|
"HeaderFavoriteEpisodes": "Kedvenc epizódok",
|
||||||
"HeaderFavoriteShows": "Kedvenc Sorozatok",
|
"HeaderFavoriteShows": "Kedvenc sorozatok",
|
||||||
"HeaderFavoriteSongs": "Kedvenc Dalok",
|
"HeaderFavoriteSongs": "Kedvenc dalok",
|
||||||
"HeaderLiveTV": "Élő TV",
|
"HeaderLiveTV": "Élő TV",
|
||||||
"HeaderNextUp": "Következik",
|
"HeaderNextUp": "Következik",
|
||||||
"HeaderRecordingGroups": "Felvételi csoportok",
|
"HeaderRecordingGroups": "Felvételi csoportok",
|
||||||
@ -34,21 +34,21 @@
|
|||||||
"LabelRunningTimeValue": "Futási idő: {0}",
|
"LabelRunningTimeValue": "Futási idő: {0}",
|
||||||
"Latest": "Legújabb",
|
"Latest": "Legújabb",
|
||||||
"MessageApplicationUpdated": "Jellyfin Szerver frissítve",
|
"MessageApplicationUpdated": "Jellyfin Szerver frissítve",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Szerver frissítve lett a következőre {0}",
|
"MessageApplicationUpdatedTo": "Jellyfin Szerver frissítve lett a következőre: {0}",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "Szerver konfigurációs rész {0} frissítve",
|
"MessageNamedServerConfigurationUpdatedWithValue": "Szerver konfigurációs rész frissítve: {0}",
|
||||||
"MessageServerConfigurationUpdated": "Szerver konfiguráció frissítve",
|
"MessageServerConfigurationUpdated": "Szerver konfiguráció frissítve",
|
||||||
"MixedContent": "Vegyes tartalom",
|
"MixedContent": "Vegyes tartalom",
|
||||||
"Movies": "Filmek",
|
"Movies": "Filmek",
|
||||||
"Music": "Zene",
|
"Music": "Zene",
|
||||||
"MusicVideos": "Zenei Videók",
|
"MusicVideos": "Zenei videók",
|
||||||
"NameInstallFailed": "{0} sikertelen telepítés",
|
"NameInstallFailed": "{0} sikertelen telepítés",
|
||||||
"NameSeasonNumber": "Évad {0}",
|
"NameSeasonNumber": "Évad {0}",
|
||||||
"NameSeasonUnknown": "Ismeretlen évad",
|
"NameSeasonUnknown": "Ismeretlen évad",
|
||||||
"NewVersionIsAvailable": "Letölthető a Jellyfin Szerver új verziója.",
|
"NewVersionIsAvailable": "Letölthető a Jellyfin Szerver új verziója.",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "Új programfrissítés érhető el",
|
"NotificationOptionApplicationUpdateAvailable": "Frissítés érhető el az alkalmazáshoz",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Programfrissítés telepítve",
|
"NotificationOptionApplicationUpdateInstalled": "Alkalmazásfrissítés telepítve",
|
||||||
"NotificationOptionAudioPlayback": "Audió lejátszás elkezdve",
|
"NotificationOptionAudioPlayback": "Audió lejátszás elkezdve",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Audió lejátszás befejezve",
|
"NotificationOptionAudioPlaybackStopped": "Audió lejátszás leállítva",
|
||||||
"NotificationOptionCameraImageUploaded": "Kamera kép feltöltve",
|
"NotificationOptionCameraImageUploaded": "Kamera kép feltöltve",
|
||||||
"NotificationOptionInstallationFailed": "Telepítési hiba",
|
"NotificationOptionInstallationFailed": "Telepítési hiba",
|
||||||
"NotificationOptionNewLibraryContent": "Új tartalom hozzáadva",
|
"NotificationOptionNewLibraryContent": "Új tartalom hozzáadva",
|
||||||
@ -60,15 +60,15 @@
|
|||||||
"NotificationOptionTaskFailed": "Ütemezett feladat hiba",
|
"NotificationOptionTaskFailed": "Ütemezett feladat hiba",
|
||||||
"NotificationOptionUserLockedOut": "Felhasználó tiltva",
|
"NotificationOptionUserLockedOut": "Felhasználó tiltva",
|
||||||
"NotificationOptionVideoPlayback": "Videó lejátszás elkezdve",
|
"NotificationOptionVideoPlayback": "Videó lejátszás elkezdve",
|
||||||
"NotificationOptionVideoPlaybackStopped": "Videó lejátszás befejezve",
|
"NotificationOptionVideoPlaybackStopped": "Videó lejátszás leállítva",
|
||||||
"Photos": "Fényképek",
|
"Photos": "Fényképek",
|
||||||
"Playlists": "Lejátszási listák",
|
"Playlists": "Lejátszási listák",
|
||||||
"Plugin": "Bővítmény",
|
"Plugin": "Bővítmény",
|
||||||
"PluginInstalledWithName": "{0} telepítve",
|
"PluginInstalledWithName": "{0} telepítve",
|
||||||
"PluginUninstalledWithName": "{0} eltávolítva",
|
"PluginUninstalledWithName": "{0} eltávolítva",
|
||||||
"PluginUpdatedWithName": "{0} frissítve",
|
"PluginUpdatedWithName": "{0} frissítve",
|
||||||
"ProviderValue": "Provider: {0}",
|
"ProviderValue": "Szolgáltató: {0}",
|
||||||
"ScheduledTaskFailedWithName": "{0} hiba",
|
"ScheduledTaskFailedWithName": "{0} sikertelen",
|
||||||
"ScheduledTaskStartedWithName": "{0} elkezdve",
|
"ScheduledTaskStartedWithName": "{0} elkezdve",
|
||||||
"ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
|
"ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
|
||||||
"Shows": "Műsorok",
|
"Shows": "Műsorok",
|
||||||
@ -76,10 +76,10 @@
|
|||||||
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek próbáld újra később.",
|
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek próbáld újra később.",
|
||||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||||
"SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0} ehhez: {1}",
|
"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",
|
"Sync": "Szinkronizál",
|
||||||
"System": "Rendszer",
|
"System": "Rendszer",
|
||||||
"TvShows": "TV Műsorok",
|
"TvShows": "TV műsorok",
|
||||||
"User": "Felhasználó",
|
"User": "Felhasználó",
|
||||||
"UserCreatedWithName": "{0} felhasználó létrehozva",
|
"UserCreatedWithName": "{0} felhasználó létrehozva",
|
||||||
"UserDeletedWithName": "{0} felhasználó törölve",
|
"UserDeletedWithName": "{0} felhasználó törölve",
|
||||||
@ -88,7 +88,7 @@
|
|||||||
"UserOfflineFromDevice": "{0} kijelentkezett innen: {1}",
|
"UserOfflineFromDevice": "{0} kijelentkezett innen: {1}",
|
||||||
"UserOnlineFromDevice": "{0} online itt: {1}",
|
"UserOnlineFromDevice": "{0} online itt: {1}",
|
||||||
"UserPasswordChangedWithName": "Jelszó megváltozott a következő felhasználó számára: {0}",
|
"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}",
|
"UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt: {2}",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} befejezte 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",
|
"ValueHasBeenAddedToLibrary": "{0} hozzáadva a médiatárhoz",
|
||||||
|
1
Emby.Server.Implementations/Localization/Core/is.json
Normal file
1
Emby.Server.Implementations/Localization/Core/is.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
@ -9,13 +9,13 @@
|
|||||||
"Channels": "Canali",
|
"Channels": "Canali",
|
||||||
"ChapterNameValue": "Capitolo {0}",
|
"ChapterNameValue": "Capitolo {0}",
|
||||||
"Collections": "Collezioni",
|
"Collections": "Collezioni",
|
||||||
"DeviceOfflineWithName": "{0} è stato disconnesso",
|
"DeviceOfflineWithName": "{0} ha disconnesso",
|
||||||
"DeviceOnlineWithName": "{0} è connesso",
|
"DeviceOnlineWithName": "{0} è connesso",
|
||||||
"FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}",
|
"FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}",
|
||||||
"Favorites": "Preferiti",
|
"Favorites": "Preferiti",
|
||||||
"Folders": "Cartelle",
|
"Folders": "Cartelle",
|
||||||
"Genres": "Generi",
|
"Genres": "Generi",
|
||||||
"HeaderAlbumArtists": "Artisti Album",
|
"HeaderAlbumArtists": "Artisti dell' Album",
|
||||||
"HeaderCameraUploads": "Caricamenti Fotocamera",
|
"HeaderCameraUploads": "Caricamenti Fotocamera",
|
||||||
"HeaderContinueWatching": "Continua a guardare",
|
"HeaderContinueWatching": "Continua a guardare",
|
||||||
"HeaderFavoriteAlbums": "Album preferiti",
|
"HeaderFavoriteAlbums": "Album preferiti",
|
||||||
@ -32,7 +32,7 @@
|
|||||||
"ItemRemovedWithName": "{0} è stato rimosso dalla libreria",
|
"ItemRemovedWithName": "{0} è stato rimosso dalla libreria",
|
||||||
"LabelIpAddressValue": "Indirizzo IP: {0}",
|
"LabelIpAddressValue": "Indirizzo IP: {0}",
|
||||||
"LabelRunningTimeValue": "Durata: {0}",
|
"LabelRunningTimeValue": "Durata: {0}",
|
||||||
"Latest": "Più recenti",
|
"Latest": "Novità",
|
||||||
"MessageApplicationUpdated": "Il Server Jellyfin è stato aggiornato",
|
"MessageApplicationUpdated": "Il Server Jellyfin è stato aggiornato",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Server è stato aggiornato a {0}",
|
"MessageApplicationUpdatedTo": "Jellyfin Server è stato aggiornato a {0}",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "La sezione {0} della configurazione server è stata aggiornata",
|
"MessageNamedServerConfigurationUpdatedWithValue": "La sezione {0} della configurazione server è stata aggiornata",
|
||||||
@ -43,7 +43,7 @@
|
|||||||
"MusicVideos": "Video musicali",
|
"MusicVideos": "Video musicali",
|
||||||
"NameInstallFailed": "{0} installazione fallita",
|
"NameInstallFailed": "{0} installazione fallita",
|
||||||
"NameSeasonNumber": "Stagione {0}",
|
"NameSeasonNumber": "Stagione {0}",
|
||||||
"NameSeasonUnknown": "Stagione sconosciuto",
|
"NameSeasonUnknown": "Stagione sconosciuta",
|
||||||
"NewVersionIsAvailable": "Una nuova versione di Jellyfin Server è disponibile per il download.",
|
"NewVersionIsAvailable": "Una nuova versione di Jellyfin Server è disponibile per il download.",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "Aggiornamento dell'applicazione disponibile",
|
"NotificationOptionApplicationUpdateAvailable": "Aggiornamento dell'applicazione disponibile",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Aggiornamento dell'applicazione installato",
|
"NotificationOptionApplicationUpdateInstalled": "Aggiornamento dell'applicazione installato",
|
||||||
@ -88,9 +88,9 @@
|
|||||||
"UserOfflineFromDevice": "{0} è stato disconnesso da {1}",
|
"UserOfflineFromDevice": "{0} è stato disconnesso da {1}",
|
||||||
"UserOnlineFromDevice": "{0} è online da {1}",
|
"UserOnlineFromDevice": "{0} è online da {1}",
|
||||||
"UserPasswordChangedWithName": "La password è stata cambiata per l'utente {0}",
|
"UserPasswordChangedWithName": "La password è stata cambiata per l'utente {0}",
|
||||||
"UserPolicyUpdatedWithName": "La politica dell'utente è stata aggiornata per {0}",
|
"UserPolicyUpdatedWithName": "La policy dell'utente è stata aggiornata per {0}",
|
||||||
"UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1}",
|
"UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1} su {2}",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1}",
|
"UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1} su {2}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale",
|
"ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale",
|
||||||
"ValueSpecialEpisodeName": "Speciale - {0}",
|
"ValueSpecialEpisodeName": "Speciale - {0}",
|
||||||
"VersionNumber": "Versione {0}"
|
"VersionNumber": "Versione {0}"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Albums": "Albums",
|
"Albums": "Albums",
|
||||||
"AppDeviceValues": "App: {0}, Apparaat: {1}",
|
"AppDeviceValues": "App: {0}, Apparaat: {1}",
|
||||||
"Application": "Toepassing",
|
"Application": "Applicatie",
|
||||||
"Artists": "Artiesten",
|
"Artists": "Artiesten",
|
||||||
"AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
|
"AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
|
||||||
"Books": "Boeken",
|
"Books": "Boeken",
|
||||||
@ -30,7 +30,7 @@
|
|||||||
"Inherit": "Overerven",
|
"Inherit": "Overerven",
|
||||||
"ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek",
|
"ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek",
|
||||||
"ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek",
|
"ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek",
|
||||||
"LabelIpAddressValue": "IP adres: {0}",
|
"LabelIpAddressValue": "IP-adres: {0}",
|
||||||
"LabelRunningTimeValue": "Looptijd: {0}",
|
"LabelRunningTimeValue": "Looptijd: {0}",
|
||||||
"Latest": "Nieuwste",
|
"Latest": "Nieuwste",
|
||||||
"MessageApplicationUpdated": "Jellyfin Server is bijgewerkt",
|
"MessageApplicationUpdated": "Jellyfin Server is bijgewerkt",
|
||||||
@ -50,7 +50,7 @@
|
|||||||
"NotificationOptionAudioPlayback": "Muziek gestart",
|
"NotificationOptionAudioPlayback": "Muziek gestart",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Muziek gestopt",
|
"NotificationOptionAudioPlaybackStopped": "Muziek gestopt",
|
||||||
"NotificationOptionCameraImageUploaded": "Camera-afbeelding geüpload",
|
"NotificationOptionCameraImageUploaded": "Camera-afbeelding geüpload",
|
||||||
"NotificationOptionInstallationFailed": "Installatie mislukt",
|
"NotificationOptionInstallationFailed": "Installatie mislukking",
|
||||||
"NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd",
|
"NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd",
|
||||||
"NotificationOptionPluginError": "Plug-in fout",
|
"NotificationOptionPluginError": "Plug-in fout",
|
||||||
"NotificationOptionPluginInstalled": "Plug-in geïnstalleerd",
|
"NotificationOptionPluginInstalled": "Plug-in geïnstalleerd",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"Artists": "Umelci",
|
"Artists": "Umelci",
|
||||||
"AuthenticationSucceededWithUserName": "{0} úspešne overený",
|
"AuthenticationSucceededWithUserName": "{0} úspešne overený",
|
||||||
"Books": "Knihy",
|
"Books": "Knihy",
|
||||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
"CameraImageUploadedFrom": "Z {0} bola nahraná nová fotografia",
|
||||||
"Channels": "Kanály",
|
"Channels": "Kanály",
|
||||||
"ChapterNameValue": "Kapitola {0}",
|
"ChapterNameValue": "Kapitola {0}",
|
||||||
"Collections": "Zbierky",
|
"Collections": "Zbierky",
|
||||||
@ -15,9 +15,9 @@
|
|||||||
"Favorites": "Obľúbené",
|
"Favorites": "Obľúbené",
|
||||||
"Folders": "Priečinky",
|
"Folders": "Priečinky",
|
||||||
"Genres": "Žánre",
|
"Genres": "Žánre",
|
||||||
"HeaderAlbumArtists": "Album Artists",
|
"HeaderAlbumArtists": "Albumy umelcov",
|
||||||
"HeaderCameraUploads": "Nahrané fotografie",
|
"HeaderCameraUploads": "Nahrané fotografie",
|
||||||
"HeaderContinueWatching": "Pokračujte v pozeraní",
|
"HeaderContinueWatching": "Pokračovať v pozeraní",
|
||||||
"HeaderFavoriteAlbums": "Obľúbené albumy",
|
"HeaderFavoriteAlbums": "Obľúbené albumy",
|
||||||
"HeaderFavoriteArtists": "Obľúbení umelci",
|
"HeaderFavoriteArtists": "Obľúbení umelci",
|
||||||
"HeaderFavoriteEpisodes": "Obľúbené epizódy",
|
"HeaderFavoriteEpisodes": "Obľúbené epizódy",
|
||||||
|
@ -5,93 +5,93 @@
|
|||||||
"Artists": "Sanatçılar",
|
"Artists": "Sanatçılar",
|
||||||
"AuthenticationSucceededWithUserName": "{0} kimlik başarıyla doğrulandı",
|
"AuthenticationSucceededWithUserName": "{0} kimlik başarıyla doğrulandı",
|
||||||
"Books": "Kitaplar",
|
"Books": "Kitaplar",
|
||||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
"CameraImageUploadedFrom": "{0} 'den yeni bir kamera resmi yüklendi",
|
||||||
"Channels": "Kanallar",
|
"Channels": "Kanallar",
|
||||||
"ChapterNameValue": "Chapter {0}",
|
"ChapterNameValue": "Bölüm {0}",
|
||||||
"Collections": "Collections",
|
"Collections": "Koleksiyonlar",
|
||||||
"DeviceOfflineWithName": "{0} has disconnected",
|
"DeviceOfflineWithName": "{0} bağlantısı kesildi",
|
||||||
"DeviceOnlineWithName": "{0} is connected",
|
"DeviceOnlineWithName": "{0} bağlı",
|
||||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
"FailedLoginAttemptWithUserName": "{0} adresinden giriş başarısız oldu",
|
||||||
"Favorites": "Favorites",
|
"Favorites": "Favoriler",
|
||||||
"Folders": "Folders",
|
"Folders": "Klasörler",
|
||||||
"Genres": "Genres",
|
"Genres": "Türler",
|
||||||
"HeaderAlbumArtists": "Album Artists",
|
"HeaderAlbumArtists": "Albüm Sanatçıları",
|
||||||
"HeaderCameraUploads": "Camera Uploads",
|
"HeaderCameraUploads": "Kamera Yüklemeleri",
|
||||||
"HeaderContinueWatching": "İzlemeye Devam Et",
|
"HeaderContinueWatching": "İzlemeye Devam Et",
|
||||||
"HeaderFavoriteAlbums": "Favori Albümler",
|
"HeaderFavoriteAlbums": "Favori Albümler",
|
||||||
"HeaderFavoriteArtists": "Favorite Artists",
|
"HeaderFavoriteArtists": "Favori Sanatçılar",
|
||||||
"HeaderFavoriteEpisodes": "Favorite Episodes",
|
"HeaderFavoriteEpisodes": "Favori Bölümler",
|
||||||
"HeaderFavoriteShows": "Favori Showlar",
|
"HeaderFavoriteShows": "Favori Diziler",
|
||||||
"HeaderFavoriteSongs": "Favorite Songs",
|
"HeaderFavoriteSongs": "Favori Şarkılar",
|
||||||
"HeaderLiveTV": "Live TV",
|
"HeaderLiveTV": "Canlı TV",
|
||||||
"HeaderNextUp": "Next Up",
|
"HeaderNextUp": "Sonraki hafta",
|
||||||
"HeaderRecordingGroups": "Recording Groups",
|
"HeaderRecordingGroups": "Kayıt Grupları",
|
||||||
"HomeVideos": "Home videos",
|
"HomeVideos": "Ev videoları",
|
||||||
"Inherit": "Inherit",
|
"Inherit": "Devral",
|
||||||
"ItemAddedWithName": "{0} was added to the library",
|
"ItemAddedWithName": "{0} kütüphaneye eklendi",
|
||||||
"ItemRemovedWithName": "{0} was removed from the library",
|
"ItemRemovedWithName": "{0} kütüphaneden silindi",
|
||||||
"LabelIpAddressValue": "Ip adresi: {0}",
|
"LabelIpAddressValue": "IP adresi: {0}",
|
||||||
"LabelRunningTimeValue": "Çalışma süresi: {0}",
|
"LabelRunningTimeValue": "Çalışma süresi: {0}",
|
||||||
"Latest": "Latest",
|
"Latest": "En son",
|
||||||
"MessageApplicationUpdated": "Jellyfin Sunucusu güncellendi",
|
"MessageApplicationUpdated": "Jellyfin Sunucusu güncellendi",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
"MessageApplicationUpdatedTo": "Jellyfin Sunucusu {0} olarak güncellendi",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
|
"MessageNamedServerConfigurationUpdatedWithValue": "Sunucu ayarları kısım {0} güncellendi",
|
||||||
"MessageServerConfigurationUpdated": "Server configuration has been updated",
|
"MessageServerConfigurationUpdated": "Sunucu ayarları güncellendi",
|
||||||
"MixedContent": "Mixed content",
|
"MixedContent": "Karışık içerik",
|
||||||
"Movies": "Movies",
|
"Movies": "Filmler",
|
||||||
"Music": "Müzik",
|
"Music": "Müzik",
|
||||||
"MusicVideos": "Müzik videoları",
|
"MusicVideos": "Müzik videoları",
|
||||||
"NameInstallFailed": "{0} kurulum başarısız",
|
"NameInstallFailed": "{0} kurulumu başarısız",
|
||||||
"NameSeasonNumber": "Sezon {0}",
|
"NameSeasonNumber": "Sezon {0}",
|
||||||
"NameSeasonUnknown": "Bilinmeyen Sezon",
|
"NameSeasonUnknown": "Bilinmeyen Sezon",
|
||||||
"NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir versiyonu indirmek için hazır.",
|
"NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir versiyonu indirmek için hazır.",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "Application update available",
|
"NotificationOptionApplicationUpdateAvailable": "Uygulama güncellemesi mevcut",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
|
"NotificationOptionApplicationUpdateInstalled": "Uygulama güncellemesi yüklendi",
|
||||||
"NotificationOptionAudioPlayback": "Audio playback started",
|
"NotificationOptionAudioPlayback": "Ses çalma başladı",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
"NotificationOptionAudioPlaybackStopped": "Ses çalma durduruldu",
|
||||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
"NotificationOptionCameraImageUploaded": "Kamera fotoğrafı yüklendi",
|
||||||
"NotificationOptionInstallationFailed": "Yükleme hatası",
|
"NotificationOptionInstallationFailed": "Yükleme başarısız oldu",
|
||||||
"NotificationOptionNewLibraryContent": "New content added",
|
"NotificationOptionNewLibraryContent": "Yeni içerik eklendi",
|
||||||
"NotificationOptionPluginError": "Plugin failure",
|
"NotificationOptionPluginError": "Eklenti hatası",
|
||||||
"NotificationOptionPluginInstalled": "Plugin installed",
|
"NotificationOptionPluginInstalled": "Eklenti yüklendi",
|
||||||
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
|
"NotificationOptionPluginUninstalled": "Eklenti kaldırıldı",
|
||||||
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
|
"NotificationOptionPluginUpdateInstalled": "Eklenti güncellemesi yüklendi",
|
||||||
"NotificationOptionServerRestartRequired": "Server restart required",
|
"NotificationOptionServerRestartRequired": "Sunucu yeniden başlatma gerekli",
|
||||||
"NotificationOptionTaskFailed": "Scheduled task failure",
|
"NotificationOptionTaskFailed": "Zamanlanmış görev hatası",
|
||||||
"NotificationOptionUserLockedOut": "User locked out",
|
"NotificationOptionUserLockedOut": "Kullanıcı kitlendi",
|
||||||
"NotificationOptionVideoPlayback": "Video playback started",
|
"NotificationOptionVideoPlayback": "Video oynatma başladı",
|
||||||
"NotificationOptionVideoPlaybackStopped": "Video playback stopped",
|
"NotificationOptionVideoPlaybackStopped": "Video oynatma durduruldu",
|
||||||
"Photos": "Photos",
|
"Photos": "Fotoğraflar",
|
||||||
"Playlists": "Playlists",
|
"Playlists": "Çalma listeleri",
|
||||||
"Plugin": "Plugin",
|
"Plugin": "Eklenti",
|
||||||
"PluginInstalledWithName": "{0} was installed",
|
"PluginInstalledWithName": "{0} yüklendi",
|
||||||
"PluginUninstalledWithName": "{0} was uninstalled",
|
"PluginUninstalledWithName": "{0} kaldırıldı",
|
||||||
"PluginUpdatedWithName": "{0} was updated",
|
"PluginUpdatedWithName": "{0} güncellendi",
|
||||||
"ProviderValue": "Provider: {0}",
|
"ProviderValue": "Sağlayıcı: {0}",
|
||||||
"ScheduledTaskFailedWithName": "{0} failed",
|
"ScheduledTaskFailedWithName": "{0} başarısız oldu",
|
||||||
"ScheduledTaskStartedWithName": "{0} started",
|
"ScheduledTaskStartedWithName": "{0} başladı",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
"ServerNameNeedsToBeRestarted": "{0} yeniden başlatılması gerekiyor",
|
||||||
"Shows": "Shows",
|
"Shows": "Diziler",
|
||||||
"Songs": "Songs",
|
"Songs": "Şarkılar",
|
||||||
"StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.",
|
"StartupEmbyServerIsLoading": "Jellyfin Sunucusu yükleniyor. Lütfen kısa süre sonra tekrar deneyin.",
|
||||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
"SubtitleDownloadFailureFromForItem": "{1} için alt yazılar {0} 'dan indirilemedi",
|
||||||
"SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
|
"SubtitlesDownloadedForItem": "{0} için altyazılar indirildi",
|
||||||
"Sync": "Sync",
|
"Sync": "Eşitle",
|
||||||
"System": "System",
|
"System": "Sistem",
|
||||||
"TvShows": "TV Shows",
|
"TvShows": "Diziler",
|
||||||
"User": "User",
|
"User": "Kullanıcı",
|
||||||
"UserCreatedWithName": "User {0} has been created",
|
"UserCreatedWithName": "{0} kullanıcısı oluşturuldu",
|
||||||
"UserDeletedWithName": "User {0} has been deleted",
|
"UserDeletedWithName": "Kullanıcı {0} silindi",
|
||||||
"UserDownloadingItemWithValues": "{0} is downloading {1}",
|
"UserDownloadingItemWithValues": "{0} indiriliyor {1}",
|
||||||
"UserLockedOutWithName": "User {0} has been locked out",
|
"UserLockedOutWithName": "Kullanıcı {0} kitlendi",
|
||||||
"UserOfflineFromDevice": "{0} has disconnected from {1}",
|
"UserOfflineFromDevice": "{0}, {1} ile bağlantısı kesildi",
|
||||||
"UserOnlineFromDevice": "{0} is online from {1}",
|
"UserOnlineFromDevice": "{0}, {1} çevrimiçi",
|
||||||
"UserPasswordChangedWithName": "Password has been changed for user {0}",
|
"UserPasswordChangedWithName": "{0} kullanıcısı için şifre değiştirildi",
|
||||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
"UserPolicyUpdatedWithName": "Kullanıcı politikası {0} için güncellendi",
|
||||||
"UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}",
|
"UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
|
"UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
"ValueHasBeenAddedToLibrary": "Medya kitaplığınıza {0} eklendi",
|
||||||
"ValueSpecialEpisodeName": "Special - {0}",
|
"ValueSpecialEpisodeName": "Özel - {0}",
|
||||||
"VersionNumber": "Version {0}"
|
"VersionNumber": "Versiyon {0}"
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"Artists": "艺术家",
|
"Artists": "艺术家",
|
||||||
"AuthenticationSucceededWithUserName": "{0} 认证成功",
|
"AuthenticationSucceededWithUserName": "{0} 认证成功",
|
||||||
"Books": "书籍",
|
"Books": "书籍",
|
||||||
"CameraImageUploadedFrom": "已从 {0} 上传了一张新的相机图像",
|
"CameraImageUploadedFrom": "已从 {0} 上传了一张新的相片",
|
||||||
"Channels": "频道",
|
"Channels": "频道",
|
||||||
"ChapterNameValue": "章节 {0}",
|
"ChapterNameValue": "章节 {0}",
|
||||||
"Collections": "合集",
|
"Collections": "合集",
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"Albums": "Albums",
|
"Albums": "Albums",
|
||||||
"AppDeviceValues": "App: {0}, Device: {1}",
|
"AppDeviceValues": "App: {0}, Device: {1}",
|
||||||
"Application": "Application",
|
"Application": "Application",
|
||||||
"Artists": "Artists",
|
"Artists": "藝人",
|
||||||
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
||||||
"Books": "Books",
|
"Books": "Books",
|
||||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"Albums": "專輯",
|
"Albums": "專輯",
|
||||||
"AppDeviceValues": "應用: {0}, 裝置: {1}",
|
"AppDeviceValues": "軟體: {0}, 裝置: {1}",
|
||||||
"Application": "應用程式",
|
"Application": "應用程式",
|
||||||
"Artists": "演出者",
|
"Artists": "演出者",
|
||||||
"AuthenticationSucceededWithUserName": "{0} 成功授權",
|
"AuthenticationSucceededWithUserName": "{0} 成功授權",
|
||||||
|
@ -5,11 +5,9 @@ using System.Globalization;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
@ -27,12 +27,12 @@ namespace Emby.Server.Implementations.Middleware
|
|||||||
var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false);
|
var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false);
|
||||||
if (webSocketContext != null)
|
if (webSocketContext != null)
|
||||||
{
|
{
|
||||||
await _webSocketManager.OnWebSocketConnected(webSocketContext);
|
await _webSocketManager.OnWebSocketConnected(webSocketContext).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _next.Invoke(httpContext);
|
await _next.Invoke(httpContext).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,6 @@ using System.Net.NetworkInformation;
|
|||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using MediaBrowser.Model.Net;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Networking
|
namespace Emby.Server.Implementations.Networking
|
||||||
@ -20,6 +18,9 @@ namespace Emby.Server.Implementations.Networking
|
|||||||
private IPAddress[] _localIpAddresses;
|
private IPAddress[] _localIpAddresses;
|
||||||
private readonly object _localIpAddressSyncLock = new object();
|
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)
|
public NetworkManager(ILogger<NetworkManager> logger)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -28,10 +29,10 @@ namespace Emby.Server.Implementations.Networking
|
|||||||
NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
|
NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<string[]> LocalSubnetsFn { get; set; }
|
|
||||||
|
|
||||||
public event EventHandler NetworkChanged;
|
public event EventHandler NetworkChanged;
|
||||||
|
|
||||||
|
public Func<string[]> LocalSubnetsFn { get; set; }
|
||||||
|
|
||||||
private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
|
private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("NetworkAvailabilityChanged");
|
_logger.LogDebug("NetworkAvailabilityChanged");
|
||||||
@ -52,10 +53,7 @@ namespace Emby.Server.Implementations.Networking
|
|||||||
_macAddresses = null;
|
_macAddresses = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NetworkChanged != null)
|
NetworkChanged?.Invoke(this, EventArgs.Empty);
|
||||||
{
|
|
||||||
NetworkChanged(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
|
public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
|
||||||
@ -179,10 +177,9 @@ namespace Emby.Server.Implementations.Networking
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary<string, List<string>> _subnetLookup = new Dictionary<string, List<string>>(StringComparer.Ordinal);
|
|
||||||
private List<string> GetSubnets(string endpointFirstPart)
|
private List<string> GetSubnets(string endpointFirstPart)
|
||||||
{
|
{
|
||||||
lock (_subnetLookup)
|
lock (_subnetLookupLock)
|
||||||
{
|
{
|
||||||
if (_subnetLookup.TryGetValue(endpointFirstPart, out var subnets))
|
if (_subnetLookup.TryGetValue(endpointFirstPart, out var subnets))
|
||||||
{
|
{
|
||||||
@ -200,7 +197,11 @@ namespace Emby.Server.Implementations.Networking
|
|||||||
int subnet_Test = 0;
|
int subnet_Test = 0;
|
||||||
foreach (string part in unicastIPAddressInformation.IPv4Mask.ToString().Split('.'))
|
foreach (string part in unicastIPAddressInformation.IPv4Mask.ToString().Split('.'))
|
||||||
{
|
{
|
||||||
if (part.Equals("0")) break;
|
if (part.Equals("0", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
subnet_Test++;
|
subnet_Test++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,10 +256,10 @@ namespace Emby.Server.Implementations.Networking
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedSubnet.IndexOf('/') != -1)
|
if (normalizedSubnet.Contains('/', StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
var ipnetwork = IPNetwork.Parse(normalizedSubnet);
|
var ipNetwork = IPNetwork.Parse(normalizedSubnet);
|
||||||
if (ipnetwork.Contains(address))
|
if (ipNetwork.Contains(address))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -447,119 +448,11 @@ namespace Emby.Server.Implementations.Networking
|
|||||||
.Select(x => x.GetPhysicalAddress())
|
.Select(x => x.GetPhysicalAddress())
|
||||||
.Where(x => x != null && x != PhysicalAddress.None);
|
.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)
|
public bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask)
|
||||||
{
|
{
|
||||||
IPAddress network1 = GetNetworkAddress(address1, subnetMask);
|
IPAddress network1 = GetNetworkAddress(address1, subnetMask);
|
||||||
IPAddress network2 = GetNetworkAddress(address2, subnetMask);
|
IPAddress network2 = GetNetworkAddress(address2, subnetMask);
|
||||||
return network1.Equals(network2);
|
return network1.Equals(network2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask)
|
private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask)
|
||||||
@ -575,7 +468,7 @@ namespace Emby.Server.Implementations.Networking
|
|||||||
byte[] broadcastAddress = new byte[ipAdressBytes.Length];
|
byte[] broadcastAddress = new byte[ipAdressBytes.Length];
|
||||||
for (int i = 0; i < broadcastAddress.Length; i++)
|
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);
|
return new IPAddress(broadcastAddress);
|
||||||
@ -615,24 +508,5 @@ namespace Emby.Server.Implementations.Networking
|
|||||||
|
|
||||||
return null;
|
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>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Playlists;
|
using MediaBrowser.Controller.Playlists;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using MediaBrowser.Model.Serialization;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Playlists
|
namespace Emby.Server.Implementations.Playlists
|
||||||
{
|
{
|
||||||
@ -24,13 +24,13 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
return base.GetEligibleChildrenForRecursiveChildren(user).OfType<Playlist>();
|
return base.GetEligibleChildrenForRecursiveChildren(user).OfType<Playlist>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[JsonIgnore]
|
||||||
public override bool IsHidden => true;
|
public override bool IsHidden => true;
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[JsonIgnore]
|
||||||
public override bool SupportsInheritedParentImages => false;
|
public override bool SupportsInheritedParentImages => false;
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[JsonIgnore]
|
||||||
public override string CollectionType => MediaBrowser.Model.Entities.CollectionType.Playlists;
|
public override string CollectionType => MediaBrowser.Model.Entities.CollectionType.Playlists;
|
||||||
|
|
||||||
protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
|
protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
|
||||||
@ -48,4 +48,3 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,8 +90,7 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var folder = item as Folder;
|
if (item is Folder folder)
|
||||||
if (folder != null)
|
|
||||||
{
|
{
|
||||||
options.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlaylist)
|
options.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlaylist)
|
||||||
.Select(i => i.MediaType)
|
.Select(i => i.MediaType)
|
||||||
@ -140,7 +139,7 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
|
|
||||||
parentFolder.AddChild(playlist, CancellationToken.None);
|
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);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (options.ItemIdList.Length > 0)
|
if (options.ItemIdList.Length > 0)
|
||||||
@ -201,7 +200,7 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
|
|
||||||
var list = new List<LinkedChild>();
|
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)
|
.Where(i => i.SupportsAddingToPlaylist)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
@ -221,18 +220,18 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
SavePlaylistFile(playlist);
|
SavePlaylistFile(playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
_providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
_providerManager.QueueRefresh(
|
||||||
{
|
playlist.Id,
|
||||||
ForceSave = true
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
|
{
|
||||||
}, RefreshPriority.High);
|
ForceSave = true
|
||||||
|
},
|
||||||
|
RefreshPriority.High);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveFromPlaylist(string playlistId, IEnumerable<string> entryIds)
|
public void RemoveFromPlaylist(string playlistId, IEnumerable<string> entryIds)
|
||||||
{
|
{
|
||||||
var playlist = _libraryManager.GetItemById(playlistId) as Playlist;
|
if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
|
||||||
|
|
||||||
if (playlist == null)
|
|
||||||
{
|
{
|
||||||
throw new ArgumentException("No Playlist exists with the supplied Id");
|
throw new ArgumentException("No Playlist exists with the supplied Id");
|
||||||
}
|
}
|
||||||
@ -254,7 +253,7 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
SavePlaylistFile(playlist);
|
SavePlaylistFile(playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
_providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
_providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
{
|
{
|
||||||
ForceSave = true
|
ForceSave = true
|
||||||
|
|
||||||
@ -263,9 +262,7 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
|
|
||||||
public void MoveItem(string playlistId, string entryId, int newIndex)
|
public void MoveItem(string playlistId, string entryId, int newIndex)
|
||||||
{
|
{
|
||||||
var playlist = _libraryManager.GetItemById(playlistId) as Playlist;
|
if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
|
||||||
|
|
||||||
if (playlist == null)
|
|
||||||
{
|
{
|
||||||
throw new ArgumentException("No Playlist exists with the supplied Id");
|
throw new ArgumentException("No Playlist exists with the supplied Id");
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
public event EventHandler<GenericEventArgs<double>> TaskProgress;
|
public event EventHandler<GenericEventArgs<double>> TaskProgress;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the scheduled task.
|
/// Gets the scheduled task.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The scheduled task.</value>
|
/// <value>The scheduled task.</value>
|
||||||
public IScheduledTask ScheduledTask { get; private set; }
|
public IScheduledTask ScheduledTask { get; private set; }
|
||||||
@ -215,11 +215,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
public double? CurrentProgress { get; private set; }
|
public double? CurrentProgress { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _triggers
|
/// The _triggers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Tuple<TaskTriggerInfo, ITaskTrigger>[] _triggers;
|
private Tuple<TaskTriggerInfo, ITaskTrigger>[] _triggers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the triggers that define when the task will run
|
/// Gets the triggers that define when the task will run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The triggers.</value>
|
/// <value>The triggers.</value>
|
||||||
private Tuple<TaskTriggerInfo, ITaskTrigger>[] InternalTriggers
|
private Tuple<TaskTriggerInfo, ITaskTrigger>[] InternalTriggers
|
||||||
@ -245,7 +246,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the triggers that define when the task will run
|
/// Gets the triggers that define when the task will run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The triggers.</value>
|
/// <value>The triggers.</value>
|
||||||
/// <exception cref="ArgumentNullException">value</exception>
|
/// <exception cref="ArgumentNullException">value</exception>
|
||||||
|
@ -36,19 +36,19 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
/// Gets or sets the json serializer.
|
/// Gets or sets the json serializer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The json serializer.</value>
|
/// <value>The json serializer.</value>
|
||||||
private IJsonSerializer JsonSerializer { get; set; }
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the application paths.
|
/// Gets or sets the application paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The application paths.</value>
|
/// <value>The application paths.</value>
|
||||||
private IApplicationPaths ApplicationPaths { get; set; }
|
private readonly IApplicationPaths _applicationPaths;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the logger.
|
/// Gets the logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The logger.</value>
|
/// <value>The logger.</value>
|
||||||
private ILogger Logger { get; set; }
|
private readonly ILogger _logger;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -57,19 +57,19 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
/// <param name="applicationPaths">The application paths.</param>
|
/// <param name="applicationPaths">The application paths.</param>
|
||||||
/// <param name="jsonSerializer">The json serializer.</param>
|
/// <param name="jsonSerializer">The json serializer.</param>
|
||||||
/// <param name="loggerFactory">The logger factory.</param>
|
/// <param name="loggerFactory">The logger factory.</param>
|
||||||
/// <exception cref="System.ArgumentException">kernel</exception>
|
/// <param name="fileSystem">The filesystem manager.</param>
|
||||||
public TaskManager(
|
public TaskManager(
|
||||||
IApplicationPaths applicationPaths,
|
IApplicationPaths applicationPaths,
|
||||||
IJsonSerializer jsonSerializer,
|
IJsonSerializer jsonSerializer,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IFileSystem fileSystem)
|
IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
ApplicationPaths = applicationPaths;
|
_applicationPaths = applicationPaths;
|
||||||
JsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
Logger = loggerFactory.CreateLogger(nameof(TaskManager));
|
_logger = loggerFactory.CreateLogger(nameof(TaskManager));
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
|
||||||
ScheduledTasks = new IScheduledTaskWorker[] { };
|
ScheduledTasks = Array.Empty<IScheduledTaskWorker>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
/// <typeparam name="T"></typeparam>
|
/// <typeparam name="T"></typeparam>
|
||||||
/// <param name="options">Task options.</param>
|
/// <param name="options">Task options.</param>
|
||||||
public void CancelIfRunningAndQueue<T>(TaskOptions options)
|
public void CancelIfRunningAndQueue<T>(TaskOptions options)
|
||||||
where T : IScheduledTask
|
where T : IScheduledTask
|
||||||
{
|
{
|
||||||
var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
|
var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
|
||||||
((ScheduledTaskWorker)task).CancelIfRunning();
|
((ScheduledTaskWorker)task).CancelIfRunning();
|
||||||
@ -115,7 +115,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
|
|
||||||
if (scheduledTask == null)
|
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
|
else
|
||||||
{
|
{
|
||||||
@ -147,13 +147,13 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
|
|
||||||
if (scheduledTask == null)
|
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
|
else
|
||||||
{
|
{
|
||||||
var type = scheduledTask.ScheduledTask.GetType();
|
var type = scheduledTask.ScheduledTask.GetType();
|
||||||
|
|
||||||
Logger.LogInformation("Queueing task {0}", type.Name);
|
_logger.LogInformation("Queueing task {0}", type.Name);
|
||||||
|
|
||||||
lock (_taskQueue)
|
lock (_taskQueue)
|
||||||
{
|
{
|
||||||
@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
|
|
||||||
if (scheduledTask == null)
|
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
|
else
|
||||||
{
|
{
|
||||||
@ -193,7 +193,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
{
|
{
|
||||||
var type = task.ScheduledTask.GetType();
|
var type = task.ScheduledTask.GetType();
|
||||||
|
|
||||||
Logger.LogInformation("Queueing task {0}", type.Name);
|
_logger.LogInformation("Queueing task {0}", type.Name);
|
||||||
|
|
||||||
lock (_taskQueue)
|
lock (_taskQueue)
|
||||||
{
|
{
|
||||||
@ -213,7 +213,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
/// <param name="tasks">The tasks.</param>
|
/// <param name="tasks">The tasks.</param>
|
||||||
public void AddTasks(IEnumerable<IScheduledTask> tasks)
|
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();
|
ScheduledTasks = ScheduledTasks.Concat(list).ToArray();
|
||||||
}
|
}
|
||||||
@ -281,7 +281,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void ExecuteQueuedTasks()
|
private void ExecuteQueuedTasks()
|
||||||
{
|
{
|
||||||
Logger.LogInformation("ExecuteQueuedTasks");
|
_logger.LogInformation("ExecuteQueuedTasks");
|
||||||
|
|
||||||
// Execute queued tasks
|
// Execute queued tasks
|
||||||
lock (_taskQueue)
|
lock (_taskQueue)
|
||||||
|
@ -19,16 +19,17 @@ using Microsoft.Extensions.Logging;
|
|||||||
namespace Emby.Server.Implementations.ScheduledTasks
|
namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class ChapterImagesTask
|
/// Class ChapterImagesTask.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ChapterImagesTask : IScheduledTask
|
public class ChapterImagesTask : IScheduledTask
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _logger
|
/// The _logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _library manager
|
/// The _library manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
@ -53,12 +54,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the triggers that define when the task will run
|
/// Creates the triggers that define when the task will run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||||
{
|
{
|
||||||
return new[] {
|
return new[]
|
||||||
|
{
|
||||||
new TaskTriggerInfo
|
new TaskTriggerInfo
|
||||||
{
|
{
|
||||||
Type = TaskTriggerInfo.TriggerDaily,
|
Type = TaskTriggerInfo.TriggerDaily,
|
||||||
@ -117,7 +118,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
previouslyFailedImages = new List<string>();
|
previouslyFailedImages = new List<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var directoryService = new DirectoryService(_logger, _fileSystem);
|
var directoryService = new DirectoryService(_fileSystem);
|
||||||
|
|
||||||
foreach (var video in videos)
|
foreach (var video in videos)
|
||||||
{
|
{
|
||||||
|
@ -4,6 +4,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -15,24 +16,18 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask
|
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 ILogger _logger;
|
||||||
|
private readonly IConfigurationManager _configurationManager;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DeleteTranscodeFileTask" /> class.
|
/// Initializes a new instance of the <see cref="DeleteTranscodeFileTask" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DeleteTranscodeFileTask(ServerApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem)
|
public DeleteTranscodeFileTask(ILogger logger, IFileSystem fileSystem, IConfigurationManager configurationManager)
|
||||||
{
|
{
|
||||||
ApplicationPaths = appPaths;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
_configurationManager = configurationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -52,14 +47,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
|||||||
var minDateModified = DateTime.UtcNow.AddDays(-1);
|
var minDateModified = DateTime.UtcNow.AddDays(-1);
|
||||||
progress.Report(50);
|
progress.Report(50);
|
||||||
|
|
||||||
try
|
DeleteTempFilesFromDirectory(cancellationToken, _configurationManager.GetTranscodePath(), minDateModified, progress);
|
||||||
{
|
|
||||||
DeleteTempFilesFromDirectory(cancellationToken, ApplicationPaths.TranscodingTempPath, minDateModified, progress);
|
|
||||||
}
|
|
||||||
catch (DirectoryNotFoundException)
|
|
||||||
{
|
|
||||||
// No biggie here. Nothing to delete
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
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 Category => "Maintenance";
|
||||||
|
|
||||||
public string Key => "DeleteTranscodingTempFiles";
|
public string Key => "DeleteTranscodeFiles";
|
||||||
|
|
||||||
public bool IsHidden => false;
|
public bool IsHidden => false;
|
||||||
|
|
||||||
|
@ -1,24 +1,23 @@
|
|||||||
using MediaBrowser.Common.Updates;
|
|
||||||
using MediaBrowser.Model.Net;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Progress;
|
using MediaBrowser.Common.Updates;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.ScheduledTasks
|
namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Plugin Update Task
|
/// Plugin Update Task.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PluginUpdateTask : IScheduledTask, IConfigurableScheduledTask
|
public class PluginUpdateTask : IScheduledTask, IConfigurableScheduledTask
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _logger
|
/// The _logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
@ -31,7 +30,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the triggers that define when the task will run
|
/// Creates the triggers that define when the task will run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||||
@ -44,16 +43,18 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update installed plugins
|
/// Update installed plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <param name="progress">The progress.</param>
|
/// <param name="progress">The progress.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns><see cref="Task" />.</returns>
|
||||||
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||||
{
|
{
|
||||||
progress.Report(0);
|
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);
|
progress.Report(10);
|
||||||
|
|
||||||
@ -94,18 +95,25 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
progress.Report(100);
|
progress.Report(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string Name => "Check for plugin updates";
|
public string Name => "Check for plugin updates";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string Description => "Downloads and installs updates for plugins that are configured to update automatically.";
|
public string Description => "Downloads and installs updates for plugins that are configured to update automatically.";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string Category => "Application";
|
public string Category => "Application";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string Key => "PluginUpdates";
|
public string Key => "PluginUpdates";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool IsHidden => false;
|
public bool IsHidden => false;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool IsEnabled => true;
|
public bool IsEnabled => true;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool IsLogged => true;
|
public bool IsLogged => true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -7,12 +6,12 @@ using Microsoft.Extensions.Logging;
|
|||||||
namespace Emby.Server.Implementations.ScheduledTasks
|
namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a task trigger that fires everyday
|
/// Represents a task trigger that fires everyday.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DailyTrigger : ITaskTrigger
|
public class DailyTrigger : ITaskTrigger
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the time of day to trigger the task to run
|
/// Get the time of day to trigger the task to run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The time of day.</value>
|
/// <value>The time of day.</value>
|
||||||
public TimeSpan TimeOfDay { get; set; }
|
public TimeSpan TimeOfDay { get; set; }
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Concurrent;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Serialization
|
namespace Emby.Server.Implementations.Serialization
|
||||||
{
|
{
|
||||||
@ -14,35 +12,13 @@ namespace Emby.Server.Implementations.Serialization
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class MyXmlSerializer : IXmlSerializer
|
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
|
// Need to cache these
|
||||||
// http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
|
// http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
|
||||||
private readonly Dictionary<string, XmlSerializer> _serializers =
|
private static readonly ConcurrentDictionary<string, XmlSerializer> _serializers =
|
||||||
new Dictionary<string, XmlSerializer>();
|
new ConcurrentDictionary<string, XmlSerializer>();
|
||||||
|
|
||||||
private XmlSerializer GetSerializer(Type type)
|
private static XmlSerializer GetSerializer(Type type)
|
||||||
{
|
=> _serializers.GetOrAdd(type.FullName, _ => new XmlSerializer(type));
|
||||||
var key = type.FullName;
|
|
||||||
lock (_serializers)
|
|
||||||
{
|
|
||||||
if (!_serializers.TryGetValue(key, out var serializer))
|
|
||||||
{
|
|
||||||
serializer = new XmlSerializer(type);
|
|
||||||
_serializers[key] = serializer;
|
|
||||||
}
|
|
||||||
return serializer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serializes to writer.
|
/// Serializes to writer.
|
||||||
@ -91,7 +67,6 @@ namespace Emby.Server.Implementations.Serialization
|
|||||||
/// <param name="file">The file.</param>
|
/// <param name="file">The file.</param>
|
||||||
public void SerializeToFile(object obj, string file)
|
public void SerializeToFile(object obj, string file)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Serializing to file {0}", file);
|
|
||||||
using (var stream = new FileStream(file, FileMode.Create))
|
using (var stream = new FileStream(file, FileMode.Create))
|
||||||
{
|
{
|
||||||
SerializeToStream(obj, stream);
|
SerializeToStream(obj, stream);
|
||||||
@ -106,7 +81,6 @@ namespace Emby.Server.Implementations.Serialization
|
|||||||
/// <returns>System.Object.</returns>
|
/// <returns>System.Object.</returns>
|
||||||
public object DeserializeFromFile(Type type, string file)
|
public object DeserializeFromFile(Type type, string file)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Deserializing file {0}", file);
|
|
||||||
using (var stream = File.OpenRead(file))
|
using (var stream = File.OpenRead(file))
|
||||||
{
|
{
|
||||||
return DeserializeFromStream(type, stream);
|
return DeserializeFromStream(type, stream);
|
@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Emby.Server.Implementations.AppBase;
|
using Emby.Server.Implementations.AppBase;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
@ -6,12 +5,10 @@ using MediaBrowser.Controller;
|
|||||||
namespace Emby.Server.Implementations
|
namespace Emby.Server.Implementations
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths
|
public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths
|
||||||
{
|
{
|
||||||
private string _defaultTranscodingTempPath;
|
|
||||||
private string _transcodingTempPath;
|
|
||||||
private string _internalMetadataPath;
|
private string _internalMetadataPath;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -23,7 +20,8 @@ namespace Emby.Server.Implementations
|
|||||||
string configurationDirectoryPath,
|
string configurationDirectoryPath,
|
||||||
string cacheDirectoryPath,
|
string cacheDirectoryPath,
|
||||||
string webDirectoryPath)
|
string webDirectoryPath)
|
||||||
: base(programDataPath,
|
: base(
|
||||||
|
programDataPath,
|
||||||
logDirectoryPath,
|
logDirectoryPath,
|
||||||
configurationDirectoryPath,
|
configurationDirectoryPath,
|
||||||
cacheDirectoryPath,
|
cacheDirectoryPath,
|
||||||
@ -31,8 +29,6 @@ namespace Emby.Server.Implementations
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the base root media directory.
|
/// Gets the path to the base root media directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -45,18 +41,13 @@ namespace Emby.Server.Implementations
|
|||||||
/// <value>The default user views path.</value>
|
/// <value>The default user views path.</value>
|
||||||
public string DefaultUserViewsPath => Path.Combine(RootFolderPath, "default");
|
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>
|
/// <summary>
|
||||||
/// Gets the path to the People directory.
|
/// Gets the path to the People directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The people path.</value>
|
/// <value>The people path.</value>
|
||||||
public string PeoplePath => Path.Combine(InternalMetadataPath, "People");
|
public string PeoplePath => Path.Combine(InternalMetadataPath, "People");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string ArtistsPath => Path.Combine(InternalMetadataPath, "artists");
|
public string ArtistsPath => Path.Combine(InternalMetadataPath, "artists");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -107,46 +98,14 @@ namespace Emby.Server.Implementations
|
|||||||
/// <value>The user configuration directory path.</value>
|
/// <value>The user configuration directory path.</value>
|
||||||
public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users");
|
public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users");
|
||||||
|
|
||||||
public string DefaultTranscodingTempPath => _defaultTranscodingTempPath ?? (_defaultTranscodingTempPath = Path.Combine(ProgramDataPath, "transcoding-temp"));
|
/// <inheritdoc />
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string InternalMetadataPath
|
public string InternalMetadataPath
|
||||||
{
|
{
|
||||||
get => _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata"));
|
get => _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata"));
|
||||||
set => _internalMetadataPath = value;
|
set => _internalMetadataPath = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string VirtualInternalMetadataPath { get; } = "%MetadataPath%";
|
public string VirtualInternalMetadataPath { get; } = "%MetadataPath%";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Services
|
namespace Emby.Server.Implementations.Services
|
||||||
{
|
{
|
||||||
@ -28,6 +30,13 @@ namespace Emby.Server.Implementations.Services
|
|||||||
private readonly bool[] isWildcard;
|
private readonly bool[] isWildcard;
|
||||||
private readonly int wildcardCount = 0;
|
private readonly int wildcardCount = 0;
|
||||||
|
|
||||||
|
internal static string[] IgnoreAttributesNamed = new[]
|
||||||
|
{
|
||||||
|
nameof(JsonIgnoreAttribute)
|
||||||
|
};
|
||||||
|
|
||||||
|
private static Type _excludeType = typeof(Stream);
|
||||||
|
|
||||||
public int VariableArgsCount { get; set; }
|
public int VariableArgsCount { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -37,8 +46,8 @@ namespace Emby.Server.Implementations.Services
|
|||||||
public int PathComponentsCount { get; set; }
|
public int PathComponentsCount { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The total number of segments after subparts have been exploded ('.')
|
/// Gets or sets the total number of segments after subparts have been exploded ('.')
|
||||||
/// e.g. /path/to/here.ext == 4
|
/// e.g. /path/to/here.ext == 4.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int TotalComponentsCount { get; set; }
|
public int TotalComponentsCount { get; set; }
|
||||||
|
|
||||||
@ -190,21 +199,12 @@ namespace Emby.Server.Implementations.Services
|
|||||||
StringComparer.OrdinalIgnoreCase);
|
StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string[] IgnoreAttributesNamed = new[]
|
|
||||||
{
|
|
||||||
"IgnoreDataMemberAttribute",
|
|
||||||
"JsonIgnoreAttribute"
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
private static Type excludeType = typeof(Stream);
|
|
||||||
|
|
||||||
internal static IEnumerable<PropertyInfo> GetSerializableProperties(Type type)
|
internal static IEnumerable<PropertyInfo> GetSerializableProperties(Type type)
|
||||||
{
|
{
|
||||||
foreach (var prop in GetPublicProperties(type))
|
foreach (var prop in GetPublicProperties(type))
|
||||||
{
|
{
|
||||||
if (prop.GetMethod == null
|
if (prop.GetMethod == null
|
||||||
|| excludeType == prop.PropertyType)
|
|| _excludeType == prop.PropertyType)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -280,7 +280,7 @@ namespace Emby.Server.Implementations.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public string FirstMatchHashKey { get; private set; }
|
public string FirstMatchHashKey { get; private set; }
|
||||||
|
|
||||||
@ -437,9 +437,12 @@ namespace Emby.Server.Implementations.Services
|
|||||||
&& requestComponents.Length >= this.TotalComponentsCount - this.wildcardCount;
|
&& requestComponents.Length >= this.TotalComponentsCount - this.wildcardCount;
|
||||||
|
|
||||||
if (!isValidWildCardPath)
|
if (!isValidWildCardPath)
|
||||||
throw new ArgumentException(string.Format(
|
throw new ArgumentException(
|
||||||
"Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'",
|
string.Format(
|
||||||
pathInfo, this.restPath));
|
CultureInfo.InvariantCulture,
|
||||||
|
"Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'",
|
||||||
|
pathInfo,
|
||||||
|
this.restPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
var requestKeyValuesMap = new Dictionary<string, string>();
|
var requestKeyValuesMap = new Dictionary<string, string>();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.Services
|
|||||||
|
|
||||||
private SwaggerTag[] GetTags()
|
private SwaggerTag[] GetTags()
|
||||||
{
|
{
|
||||||
return new SwaggerTag[] { };
|
return Array.Empty<SwaggerTag>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary<string, SwaggerDefinition> GetDefinitions()
|
private Dictionary<string, SwaggerDefinition> GetDefinitions()
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user