From c0a8118c51b7211c5e8d21bf9084890cb9e8ac8d Mon Sep 17 00:00:00 2001 From: Cromefire_ Date: Fri, 28 Aug 2020 21:19:42 +0200 Subject: [PATCH 001/278] Added additional opts to service file --- debian/jellyfin.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/jellyfin.service b/debian/jellyfin.service index f1a8f4652cf..5a012d2f4a2 100644 --- a/debian/jellyfin.service +++ b/debian/jellyfin.service @@ -6,7 +6,7 @@ After = network.target Type = simple EnvironmentFile = /etc/default/jellyfin User = jellyfin -ExecStart = /usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} +ExecStart = /usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} $JELLYFIN_ADDITIONAL_OPTS Restart = on-failure TimeoutSec = 15 From 20b1f985f07ff3b8c3f6ecfc7cf66be3e8ab1d4b Mon Sep 17 00:00:00 2001 From: Cromefire_ Date: Fri, 28 Aug 2020 21:24:21 +0200 Subject: [PATCH 002/278] Added JELLYFIN_ADDITIONAL_OPTS to default file --- debian/conf/jellyfin | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/debian/conf/jellyfin b/debian/conf/jellyfin index 7cbfa88ee8c..fa8cc718acb 100644 --- a/debian/conf/jellyfin +++ b/debian/conf/jellyfin @@ -33,6 +33,9 @@ JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg" # [OPTIONAL] run Jellyfin without the web app #JELLYFIN_NOWEBAPP_OPT="--nowebclient" +# Space to add additional command line options to jellyfin (for help see ~$ jellyfin --help) +JELLYFIN_ADDITIONAL_OPTS="" + # # SysV init/Upstart options # @@ -40,4 +43,4 @@ JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg" # Application username JELLYFIN_USER="jellyfin" # Full application command -JELLYFIN_ARGS="$JELLYFIN_WEB_OPT $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLYFIN_NOWEBAPP_OPT" +JELLYFIN_ARGS="${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} $JELLFIN_ADDITIONAL_OPTS" From 18974550041a4c83203c0bb36936107895b04389 Mon Sep 17 00:00:00 2001 From: Cromefire_ Date: Mon, 23 Nov 2020 20:17:40 +0100 Subject: [PATCH 003/278] Update debian/conf/jellyfin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Odd Stråbø --- debian/conf/jellyfin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/conf/jellyfin b/debian/conf/jellyfin index fa8cc718acb..ced26d6bb2f 100644 --- a/debian/conf/jellyfin +++ b/debian/conf/jellyfin @@ -43,4 +43,4 @@ JELLYFIN_ADDITIONAL_OPTS="" # Application username JELLYFIN_USER="jellyfin" # Full application command -JELLYFIN_ARGS="${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} $JELLFIN_ADDITIONAL_OPTS" +JELLYFIN_ARGS="${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} ${JELLFIN_ADDITIONAL_OPTS}" From 7396fcfb847229e580ae00793c6344e70b4c9141 Mon Sep 17 00:00:00 2001 From: Cromefire_ Date: Tue, 24 Nov 2020 12:23:44 +0100 Subject: [PATCH 004/278] Removed bash style vars --- debian/conf/jellyfin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/conf/jellyfin b/debian/conf/jellyfin index ced26d6bb2f..93373aeba7a 100644 --- a/debian/conf/jellyfin +++ b/debian/conf/jellyfin @@ -43,4 +43,4 @@ JELLYFIN_ADDITIONAL_OPTS="" # Application username JELLYFIN_USER="jellyfin" # Full application command -JELLYFIN_ARGS="${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} ${JELLFIN_ADDITIONAL_OPTS}" +JELLYFIN_ARGS="$JELLYFIN_WEB_OPT $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLYFIN_NOWEBAPP_OPT $JELLFIN_ADDITIONAL_OPTS" From 7aef0fce444e6d8e06386553ec7ea1401a01bbb1 Mon Sep 17 00:00:00 2001 From: Cromefire_ Date: Tue, 24 Nov 2020 12:24:42 +0100 Subject: [PATCH 005/278] Use consistent style --- debian/jellyfin.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/jellyfin.service b/debian/jellyfin.service index 5a012d2f4a2..459ac6363df 100644 --- a/debian/jellyfin.service +++ b/debian/jellyfin.service @@ -6,7 +6,7 @@ After = network.target Type = simple EnvironmentFile = /etc/default/jellyfin User = jellyfin -ExecStart = /usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} $JELLYFIN_ADDITIONAL_OPTS +ExecStart = /usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} ${JELLYFIN_ADDITIONAL_OPTS} Restart = on-failure TimeoutSec = 15 From a4e1732e3553b7c039d23f89082fe23b058aac59 Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Sun, 29 Nov 2020 03:39:28 -0500 Subject: [PATCH 006/278] Fix restart.sh to look at what's actually booted The old code was wrong because e.g. systemd can be *installed* on the system, but not actually used as PID1. In that case we would pick `systemctl`, but it wouldn't actually work because PID1 was some other init system. --- debian/bin/restart.sh | 29 +++++++++++++++++++++-------- fedora/restart.sh | 29 +++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/debian/bin/restart.sh b/debian/bin/restart.sh index 34fce067083..acbec3dc783 100755 --- a/debian/bin/restart.sh +++ b/debian/bin/restart.sh @@ -11,16 +11,29 @@ # # This script is used by the Debian/Ubuntu/Fedora/CentOS packages. -get_service_command() { - for command in systemctl service; do - if which $command &>/dev/null; then - echo $command && return +# This is the Right Way(tm) to check if we are booted with +# systemd, according to sd_booted(3) +if [ -d /run/systemd/system ]; then + cmd=systemctl +else + # Everything else is really hard to figure out, so we just use + # service(8) if it's available - that works with most init + # systems/distributions I know of, including FreeBSD + if type service >/dev/null 2>&1; then + cmd=service + else + # If even service(8) isn't available, we just try /etc/init.d + # and hope for the best + if [ -d /etc/init.d ]; then + cmd=sysv + else + echo "Unable to detect a way to restart Jellyfin; bailing out" 1>&2 + echo "Please report this bug to https://github.com/jellyfin/jellyfin/issues" 1>&2 + exit 1 fi - done - echo "sysv" -} + fi +fi -cmd="$( get_service_command )" echo "Detected service control platform '$cmd'; using it to restart Jellyfin..." case $cmd in 'systemctl') diff --git a/fedora/restart.sh b/fedora/restart.sh index 34fce067083..acbec3dc783 100755 --- a/fedora/restart.sh +++ b/fedora/restart.sh @@ -11,16 +11,29 @@ # # This script is used by the Debian/Ubuntu/Fedora/CentOS packages. -get_service_command() { - for command in systemctl service; do - if which $command &>/dev/null; then - echo $command && return +# This is the Right Way(tm) to check if we are booted with +# systemd, according to sd_booted(3) +if [ -d /run/systemd/system ]; then + cmd=systemctl +else + # Everything else is really hard to figure out, so we just use + # service(8) if it's available - that works with most init + # systems/distributions I know of, including FreeBSD + if type service >/dev/null 2>&1; then + cmd=service + else + # If even service(8) isn't available, we just try /etc/init.d + # and hope for the best + if [ -d /etc/init.d ]; then + cmd=sysv + else + echo "Unable to detect a way to restart Jellyfin; bailing out" 1>&2 + echo "Please report this bug to https://github.com/jellyfin/jellyfin/issues" 1>&2 + exit 1 fi - done - echo "sysv" -} + fi +fi -cmd="$( get_service_command )" echo "Detected service control platform '$cmd'; using it to restart Jellyfin..." case $cmd in 'systemctl') From ce82932c9a4a33fd142d56e5b0683429329751ee Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Sun, 29 Nov 2020 03:47:18 -0500 Subject: [PATCH 007/278] Remove useless which(1) calls in restart.sh at(1) runs commandlines with /bin/sh anyway, which resolves paths. No need to do it ourselves. --- debian/bin/restart.sh | 4 ++-- fedora/restart.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/debian/bin/restart.sh b/debian/bin/restart.sh index acbec3dc783..46a70c497e1 100755 --- a/debian/bin/restart.sh +++ b/debian/bin/restart.sh @@ -37,10 +37,10 @@ fi echo "Detected service control platform '$cmd'; using it to restart Jellyfin..." case $cmd in 'systemctl') - echo "sleep 0.5; /usr/bin/sudo $( which systemctl ) start jellyfin" | at now + echo "sleep 0.5; /usr/bin/sudo systemctl start jellyfin" | at now ;; 'service') - echo "sleep 0.5; /usr/bin/sudo $( which service ) jellyfin start" | at now + echo "sleep 0.5; /usr/bin/sudo service jellyfin start" | at now ;; 'sysv') echo "sleep 0.5; /usr/bin/sudo /etc/init.d/jellyfin start" | at now diff --git a/fedora/restart.sh b/fedora/restart.sh index acbec3dc783..46a70c497e1 100755 --- a/fedora/restart.sh +++ b/fedora/restart.sh @@ -37,10 +37,10 @@ fi echo "Detected service control platform '$cmd'; using it to restart Jellyfin..." case $cmd in 'systemctl') - echo "sleep 0.5; /usr/bin/sudo $( which systemctl ) start jellyfin" | at now + echo "sleep 0.5; /usr/bin/sudo systemctl start jellyfin" | at now ;; 'service') - echo "sleep 0.5; /usr/bin/sudo $( which service ) jellyfin start" | at now + echo "sleep 0.5; /usr/bin/sudo service jellyfin start" | at now ;; 'sysv') echo "sleep 0.5; /usr/bin/sudo /etc/init.d/jellyfin start" | at now From 2911dfc37d79bb4069a0ca1272ee6609f0400a39 Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Sun, 29 Nov 2020 03:48:56 -0500 Subject: [PATCH 008/278] Don't restart with sudo(8) if it's not available Some environments, like system containers, have no reason to have sudo(8) installed. In these environments restart.sh will silently fail because /usr/bin/sudo does not exist to execute, so test that sudo exists and don't try to use it otherwise. Note also that hardcoding sudo's path is wrong: it can be installed in other places. On FreeBSD, for example, it is /usr/local/bin/sudo when installed from ports. --- debian/bin/restart.sh | 10 ++++++++-- fedora/restart.sh | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/debian/bin/restart.sh b/debian/bin/restart.sh index 46a70c497e1..6aea24ee492 100755 --- a/debian/bin/restart.sh +++ b/debian/bin/restart.sh @@ -34,13 +34,19 @@ else fi fi +if type sudo >/dev/null 2>&1; then + sudo_command=sudo +else + sudo_command= +fi + echo "Detected service control platform '$cmd'; using it to restart Jellyfin..." case $cmd in 'systemctl') - echo "sleep 0.5; /usr/bin/sudo systemctl start jellyfin" | at now + echo "sleep 0.5; $sudo_command systemctl start jellyfin" | at now ;; 'service') - echo "sleep 0.5; /usr/bin/sudo service jellyfin start" | at now + echo "sleep 0.5; $sudo_command service jellyfin start" | at now ;; 'sysv') echo "sleep 0.5; /usr/bin/sudo /etc/init.d/jellyfin start" | at now diff --git a/fedora/restart.sh b/fedora/restart.sh index 46a70c497e1..6aea24ee492 100755 --- a/fedora/restart.sh +++ b/fedora/restart.sh @@ -34,13 +34,19 @@ else fi fi +if type sudo >/dev/null 2>&1; then + sudo_command=sudo +else + sudo_command= +fi + echo "Detected service control platform '$cmd'; using it to restart Jellyfin..." case $cmd in 'systemctl') - echo "sleep 0.5; /usr/bin/sudo systemctl start jellyfin" | at now + echo "sleep 0.5; $sudo_command systemctl start jellyfin" | at now ;; 'service') - echo "sleep 0.5; /usr/bin/sudo service jellyfin start" | at now + echo "sleep 0.5; $sudo_command service jellyfin start" | at now ;; 'sysv') echo "sleep 0.5; /usr/bin/sudo /etc/init.d/jellyfin start" | at now From b528816b2a59c295ff37b8ca24fbc964094e1272 Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Sun, 29 Nov 2020 04:13:03 -0500 Subject: [PATCH 009/278] Add sudo to package dependencies It's used in the restart.sh script. For Debian, this is a Recommends because virtually everyone will need this (default APT policy is to install recommended packages so this works ok), but technically you can configure the server to run as root and then you wouldn't need it. For Fedora... frankly I got confused by their Weak Dependencies etc. so I just made it a hard dependency. --- debian/control | 2 +- fedora/jellyfin.spec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/control b/debian/control index 9675d36ca67..51b20c670d5 100644 --- a/debian/control +++ b/debian/control @@ -23,6 +23,6 @@ Depends: at, libfontconfig1, libfreetype6, libssl1.1 -Recommends: jellyfin-web +Recommends: jellyfin-web, sudo Description: Jellyfin is the Free Software Media System. This package provides the Jellyfin server backend and API. diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec index 197126ee562..de259efb599 100644 --- a/fedora/jellyfin.spec +++ b/fedora/jellyfin.spec @@ -40,7 +40,7 @@ Jellyfin is a free software media system that puts you in control of managing an Summary: The Free Software Media System Server backend Requires(pre): shadow-utils Requires: ffmpeg -Requires: libcurl, fontconfig, freetype, openssl, glibc, libicu, at +Requires: libcurl, fontconfig, freetype, openssl, glibc, libicu, at, sudo %description server The Jellyfin media server backend. From d251c701b940fc1d7d1f29799785d4bc290b8ce6 Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Sun, 29 Nov 2020 04:35:22 -0500 Subject: [PATCH 010/278] Use systemd-run(1) in restart.sh systemd-run(1) runs `systemctl restart` in an isolated systemd unit that is not subject to process termination as jellyfin.service is shut down. We adjust the sudoers configuration for this new usage, removing the old config, since restart.sh is the only user of the sudoers policy. Additionally we change `systemctl start` to `systemctl restart` since there was a race condition where jellyfin.service was not fully stopped by the time this ran, so `systemctl start` became a noop. `systemctl restart` on the other hand works whether jellyfin.service is stopped or not. The at(1) hack (and the usage of `start` instead of `restart`) is left in for other init systems since I cannot test on those systems, and because I don't know of any systemd-run(1) equivalent (although it may be a non-issue since alternate init systems do not keep track of daemon children nearly as aggressively as systemd does). --- debian/bin/restart.sh | 3 ++- debian/conf/jellyfin-sudoers | 6 +++--- fedora/jellyfin.sudoers | 7 +++---- fedora/restart.sh | 3 ++- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/debian/bin/restart.sh b/debian/bin/restart.sh index 6aea24ee492..be5ca2c8bdf 100755 --- a/debian/bin/restart.sh +++ b/debian/bin/restart.sh @@ -43,7 +43,8 @@ fi echo "Detected service control platform '$cmd'; using it to restart Jellyfin..." case $cmd in 'systemctl') - echo "sleep 0.5; $sudo_command systemctl start jellyfin" | at now + # Without systemd-run here, `jellyfin.service`'s shutdown terminates this process too + $sudo_command systemd-run --scope systemctl restart jellyfin ;; 'service') echo "sleep 0.5; $sudo_command service jellyfin start" | at now diff --git a/debian/conf/jellyfin-sudoers b/debian/conf/jellyfin-sudoers index b481ba4ad4f..01e90322be9 100644 --- a/debian/conf/jellyfin-sudoers +++ b/debian/conf/jellyfin-sudoers @@ -2,9 +2,9 @@ Cmnd_Alias RESTARTSERVER_SYSV = /sbin/service jellyfin restart, /usr/sbin/service jellyfin restart Cmnd_Alias STARTSERVER_SYSV = /sbin/service jellyfin start, /usr/sbin/service jellyfin start Cmnd_Alias STOPSERVER_SYSV = /sbin/service jellyfin stop, /usr/sbin/service jellyfin stop -Cmnd_Alias RESTARTSERVER_SYSTEMD = /usr/bin/systemctl restart jellyfin, /bin/systemctl restart jellyfin -Cmnd_Alias STARTSERVER_SYSTEMD = /usr/bin/systemctl start jellyfin, /bin/systemctl start jellyfin -Cmnd_Alias STOPSERVER_SYSTEMD = /usr/bin/systemctl stop jellyfin, /bin/systemctl stop jellyfin +Cmnd_Alias RESTARTSERVER_SYSTEMD = /usr/bin/systemd-run --scope systemctl restart jellyfin +Cmnd_Alias STARTSERVER_SYSTEMD = /usr/bin/systemd-run --scope systemctl start jellyfin +Cmnd_Alias STOPSERVER_SYSTEMD = /usr/bin/systemd-run --scope systemctl stop jellyfin Cmnd_Alias RESTARTSERVER_INITD = /etc/init.d/jellyfin restart Cmnd_Alias STARTSERVER_INITD = /etc/init.d/jellyfin start Cmnd_Alias STOPSERVER_INITD = /etc/init.d/jellyfin stop diff --git a/fedora/jellyfin.sudoers b/fedora/jellyfin.sudoers index dd245af4b83..5a7054e9924 100644 --- a/fedora/jellyfin.sudoers +++ b/fedora/jellyfin.sudoers @@ -1,8 +1,7 @@ # Allow jellyfin group to start, stop and restart itself -Cmnd_Alias RESTARTSERVER_SYSTEMD = /usr/bin/systemctl restart jellyfin, /bin/systemctl restart jellyfin -Cmnd_Alias STARTSERVER_SYSTEMD = /usr/bin/systemctl start jellyfin, /bin/systemctl start jellyfin -Cmnd_Alias STOPSERVER_SYSTEMD = /usr/bin/systemctl stop jellyfin, /bin/systemctl stop jellyfin - +Cmnd_Alias RESTARTSERVER_SYSTEMD = /usr/bin/systemd-run --scope systemctl restart jellyfin +Cmnd_Alias STARTSERVER_SYSTEMD = /usr/bin/systemd-run --scope systemctl start jellyfin +Cmnd_Alias STOPSERVER_SYSTEMD = /usr/bin/systemd-run --scope systemctl stop jellyfin jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSTEMD jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSTEMD diff --git a/fedora/restart.sh b/fedora/restart.sh index 6aea24ee492..be5ca2c8bdf 100755 --- a/fedora/restart.sh +++ b/fedora/restart.sh @@ -43,7 +43,8 @@ fi echo "Detected service control platform '$cmd'; using it to restart Jellyfin..." case $cmd in 'systemctl') - echo "sleep 0.5; $sudo_command systemctl start jellyfin" | at now + # Without systemd-run here, `jellyfin.service`'s shutdown terminates this process too + $sudo_command systemd-run --scope systemctl restart jellyfin ;; 'service') echo "sleep 0.5; $sudo_command service jellyfin start" | at now From bab389114b5833735d55d982ba1a2c17fd22e0d2 Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Fri, 4 Dec 2020 16:08:49 -0800 Subject: [PATCH 011/278] Use a service unit, not a scope unit, to restart Reportedly `systemd-run --scope` still got killed by the service manager; see #4615. The suspected cause is that `scope` units are run by the `systemd-run` process itself and inherit the caller's execution environment (see systemd-run(1)). To fix this, we use a systemd `service` unit instead, which is run and managed by PID 1 - hopefully this will isolate us sufficiently so that we don't get terminated along with `jellyfin.service`. --- debian/bin/restart.sh | 2 +- debian/conf/jellyfin-sudoers | 6 +++--- fedora/jellyfin.sudoers | 6 +++--- fedora/restart.sh | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/debian/bin/restart.sh b/debian/bin/restart.sh index be5ca2c8bdf..4847b918be0 100755 --- a/debian/bin/restart.sh +++ b/debian/bin/restart.sh @@ -44,7 +44,7 @@ echo "Detected service control platform '$cmd'; using it to restart Jellyfin..." case $cmd in 'systemctl') # Without systemd-run here, `jellyfin.service`'s shutdown terminates this process too - $sudo_command systemd-run --scope systemctl restart jellyfin + $sudo_command systemd-run systemctl restart jellyfin ;; 'service') echo "sleep 0.5; $sudo_command service jellyfin start" | at now diff --git a/debian/conf/jellyfin-sudoers b/debian/conf/jellyfin-sudoers index 01e90322be9..f84e7454ff0 100644 --- a/debian/conf/jellyfin-sudoers +++ b/debian/conf/jellyfin-sudoers @@ -2,9 +2,9 @@ Cmnd_Alias RESTARTSERVER_SYSV = /sbin/service jellyfin restart, /usr/sbin/service jellyfin restart Cmnd_Alias STARTSERVER_SYSV = /sbin/service jellyfin start, /usr/sbin/service jellyfin start Cmnd_Alias STOPSERVER_SYSV = /sbin/service jellyfin stop, /usr/sbin/service jellyfin stop -Cmnd_Alias RESTARTSERVER_SYSTEMD = /usr/bin/systemd-run --scope systemctl restart jellyfin -Cmnd_Alias STARTSERVER_SYSTEMD = /usr/bin/systemd-run --scope systemctl start jellyfin -Cmnd_Alias STOPSERVER_SYSTEMD = /usr/bin/systemd-run --scope systemctl stop jellyfin +Cmnd_Alias RESTARTSERVER_SYSTEMD = /usr/bin/systemd-run systemctl restart jellyfin +Cmnd_Alias STARTSERVER_SYSTEMD = /usr/bin/systemd-run systemctl start jellyfin +Cmnd_Alias STOPSERVER_SYSTEMD = /usr/bin/systemd-run systemctl stop jellyfin Cmnd_Alias RESTARTSERVER_INITD = /etc/init.d/jellyfin restart Cmnd_Alias STARTSERVER_INITD = /etc/init.d/jellyfin start Cmnd_Alias STOPSERVER_INITD = /etc/init.d/jellyfin stop diff --git a/fedora/jellyfin.sudoers b/fedora/jellyfin.sudoers index 5a7054e9924..57a9e7b6713 100644 --- a/fedora/jellyfin.sudoers +++ b/fedora/jellyfin.sudoers @@ -1,7 +1,7 @@ # Allow jellyfin group to start, stop and restart itself -Cmnd_Alias RESTARTSERVER_SYSTEMD = /usr/bin/systemd-run --scope systemctl restart jellyfin -Cmnd_Alias STARTSERVER_SYSTEMD = /usr/bin/systemd-run --scope systemctl start jellyfin -Cmnd_Alias STOPSERVER_SYSTEMD = /usr/bin/systemd-run --scope systemctl stop jellyfin +Cmnd_Alias RESTARTSERVER_SYSTEMD = /usr/bin/systemd-run systemctl restart jellyfin +Cmnd_Alias STARTSERVER_SYSTEMD = /usr/bin/systemd-run systemctl start jellyfin +Cmnd_Alias STOPSERVER_SYSTEMD = /usr/bin/systemd-run systemctl stop jellyfin jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSTEMD jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSTEMD diff --git a/fedora/restart.sh b/fedora/restart.sh index be5ca2c8bdf..4847b918be0 100755 --- a/fedora/restart.sh +++ b/fedora/restart.sh @@ -44,7 +44,7 @@ echo "Detected service control platform '$cmd'; using it to restart Jellyfin..." case $cmd in 'systemctl') # Without systemd-run here, `jellyfin.service`'s shutdown terminates this process too - $sudo_command systemd-run --scope systemctl restart jellyfin + $sudo_command systemd-run systemctl restart jellyfin ;; 'service') echo "sleep 0.5; $sudo_command service jellyfin start" | at now From 68969c9530c42ab88da084c55cbeced8099d8ddd Mon Sep 17 00:00:00 2001 From: Ionut Andrei Oanca Date: Sun, 24 Jan 2021 01:05:17 +0100 Subject: [PATCH 012/278] Clear playlist in SyncPlay group --- Emby.Server.Implementations/SyncPlay/Group.cs | 10 ++++++++++ .../Controllers/SyncPlayController.cs | 2 +- .../RemoveFromPlaylistRequestDto.cs | 14 +++++++++++++- .../GroupStates/AbstractGroupState.cs | 11 ++++++++++- .../SyncPlay/IGroupStateContext.cs | 6 ++++++ .../RemoveFromPlaylistGroupRequest.cs | 19 ++++++++++++++++++- 6 files changed, 58 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/SyncPlay/Group.cs b/Emby.Server.Implementations/SyncPlay/Group.cs index 7c2ad2477aa..4b0b8d5f762 100644 --- a/Emby.Server.Implementations/SyncPlay/Group.cs +++ b/Emby.Server.Implementations/SyncPlay/Group.cs @@ -534,6 +534,16 @@ namespace Emby.Server.Implementations.SyncPlay return itemFound; } + /// + public void ClearPlayQueue(bool clearPlayingItem) + { + PlayQueue.ClearPlaylist(clearPlayingItem); + if (clearPlayingItem) + { + RestartCurrentItem(); + } + } + /// public bool RemoveFromPlayQueue(IReadOnlyList playlistItemIds) { diff --git a/Jellyfin.Api/Controllers/SyncPlayController.cs b/Jellyfin.Api/Controllers/SyncPlayController.cs index 82cbe58df2d..8ca75d31424 100644 --- a/Jellyfin.Api/Controllers/SyncPlayController.cs +++ b/Jellyfin.Api/Controllers/SyncPlayController.cs @@ -162,7 +162,7 @@ namespace Jellyfin.Api.Controllers [FromBody, Required] RemoveFromPlaylistRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); - var syncPlayRequest = new RemoveFromPlaylistGroupRequest(requestData.PlaylistItemIds); + var syncPlayRequest = new RemoveFromPlaylistGroupRequest(requestData.PlaylistItemIds, requestData.ClearPlaylist, requestData.ClearPlayingItem); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } diff --git a/Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs b/Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs index e9b2b2cb378..02ce5a04886 100644 --- a/Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs +++ b/Jellyfin.Api/Models/SyncPlayDtos/RemoveFromPlaylistRequestDto.cs @@ -17,9 +17,21 @@ namespace Jellyfin.Api.Models.SyncPlayDtos } /// - /// Gets or sets the playlist identifiers ot the items. + /// Gets or sets the playlist identifiers ot the items. Ignored when clearing the playlist. /// /// The playlist identifiers ot the items. public IReadOnlyList PlaylistItemIds { get; set; } + + /// + /// Gets or sets a value indicating whether the entire playlist should be cleared. + /// + /// Whether the entire playlist should be cleared. + public bool ClearPlaylist { get; set; } + + /// + /// Gets or sets a value indicating whether the playing item should be removed as well. Used only when clearing the playlist. + /// + /// Whether the playing item should be removed as well. + public bool ClearPlayingItem { get; set; } } } diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs index e3de22db38b..5e73efe6e8a 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs @@ -66,7 +66,16 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates /// public virtual void HandleRequest(RemoveFromPlaylistGroupRequest request, IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken) { - var playingItemRemoved = context.RemoveFromPlayQueue(request.PlaylistItemIds); + bool playingItemRemoved; + if (request.ClearPlaylist) + { + context.ClearPlayQueue(request.ClearPlayingItem); + playingItemRemoved = request.ClearPlayingItem; + } + else + { + playingItemRemoved = context.RemoveFromPlayQueue(request.PlaylistItemIds); + } var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.RemoveItems); var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); diff --git a/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs b/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs index aa263638aaa..ea47548f744 100644 --- a/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs +++ b/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs @@ -160,6 +160,12 @@ namespace MediaBrowser.Controller.SyncPlay /// true if the play queue has been changed; false if something went wrong. bool SetPlayingItem(Guid playlistItemId); + /// + /// Clears the play queue. + /// + /// Whether to remove the playing item as well. + void ClearPlayQueue(bool clearPlayingItem); + /// /// Removes items from the play queue. /// diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs index 47c06c22272..f9598a3ee45 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs @@ -15,9 +15,14 @@ namespace MediaBrowser.Controller.SyncPlay.PlaybackRequests /// Initializes a new instance of the class. /// /// The playlist ids of the items to remove. - public RemoveFromPlaylistGroupRequest(IReadOnlyList items) + /// Whether to clear the entire playlist. The items list will be ignored. + /// Whether to remove the playing item as well. Used only when clearing the playlist. + + public RemoveFromPlaylistGroupRequest(IReadOnlyList items, bool clearPlaylist = false, bool clearPlayingItem = false) { PlaylistItemIds = items ?? Array.Empty(); + ClearPlaylist = clearPlaylist; + ClearPlayingItem = clearPlayingItem; } /// @@ -26,6 +31,18 @@ namespace MediaBrowser.Controller.SyncPlay.PlaybackRequests /// The playlist identifiers ot the items. public IReadOnlyList PlaylistItemIds { get; } + /// + /// Gets a value indicating whether the entire playlist should be cleared. + /// + /// Whether the entire playlist should be cleared. + public bool ClearPlaylist { get; } + + /// + /// Gets a value indicating whether the playing item should be removed as well. + /// + /// Whether the playing item should be removed as well. + public bool ClearPlayingItem { get; } + /// public override PlaybackRequestType Action { get; } = PlaybackRequestType.RemoveFromPlaylist; From 9eb740ba57c7d8dfbffac9e18541c969eb0af880 Mon Sep 17 00:00:00 2001 From: Ionut Andrei Oanca Date: Sun, 28 Mar 2021 13:25:40 +0200 Subject: [PATCH 013/278] Fix storing outdated sessions in SyncPlay --- .../Session/SessionManager.cs | 6 ++-- Emby.Server.Implementations/SyncPlay/Group.cs | 30 +++++++++---------- .../SyncPlay/SyncPlayManager.cs | 8 ++--- .../Session/ISessionManager.cs | 8 ++--- .../SyncPlay/GroupMember.cs | 23 +++++++++++--- 5 files changed, 46 insertions(+), 29 deletions(-) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 10e28c33a5e..0dc87c8448d 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1197,16 +1197,18 @@ namespace Emby.Server.Implementations.Session } /// - public async Task SendSyncPlayCommand(SessionInfo session, SendCommand command, CancellationToken cancellationToken) + public async Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken) { CheckDisposed(); + var session = GetSession(sessionId); await SendMessageToSession(session, SessionMessageType.SyncPlayCommand, command, cancellationToken).ConfigureAwait(false); } /// - public async Task SendSyncPlayGroupUpdate(SessionInfo session, GroupUpdate command, CancellationToken cancellationToken) + public async Task SendSyncPlayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken) { CheckDisposed(); + var session = GetSession(sessionId); await SendMessageToSession(session, SessionMessageType.SyncPlayGroupUpdate, command, cancellationToken).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/SyncPlay/Group.cs b/Emby.Server.Implementations/SyncPlay/Group.cs index 7c2ad2477aa..2fd971c9d75 100644 --- a/Emby.Server.Implementations/SyncPlay/Group.cs +++ b/Emby.Server.Implementations/SyncPlay/Group.cs @@ -162,26 +162,26 @@ namespace Emby.Server.Implementations.SyncPlay /// /// Filters sessions of this group. /// - /// The current session. + /// The current session identifier. /// The filtering type. /// The list of sessions matching the filter. - private IEnumerable FilterSessions(SessionInfo from, SyncPlayBroadcastType type) + private IEnumerable FilterSessions(string fromId, SyncPlayBroadcastType type) { return type switch { - SyncPlayBroadcastType.CurrentSession => new SessionInfo[] { from }, + SyncPlayBroadcastType.CurrentSession => new string[] { fromId }, SyncPlayBroadcastType.AllGroup => _participants .Values - .Select(session => session.Session), + .Select(member => member.SessionId), SyncPlayBroadcastType.AllExceptCurrentSession => _participants .Values - .Select(session => session.Session) - .Where(session => !session.Id.Equals(from.Id, StringComparison.OrdinalIgnoreCase)), + .Select(member => member.SessionId) + .Where(sessionId => !sessionId.Equals(fromId, StringComparison.OrdinalIgnoreCase)), SyncPlayBroadcastType.AllReady => _participants .Values - .Where(session => !session.IsBuffering) - .Select(session => session.Session), - _ => Enumerable.Empty() + .Where(member => !member.IsBuffering) + .Select(member => member.SessionId), + _ => Enumerable.Empty() }; } @@ -223,7 +223,7 @@ namespace Emby.Server.Implementations.SyncPlay // Get list of users. var users = _participants .Values - .Select(participant => _userManager.GetUserById(participant.Session.UserId)); + .Select(participant => _userManager.GetUserById(participant.UserId)); // Find problematic users. var usersWithNoAccess = users.Where(user => !HasAccessToQueue(user, queue)); @@ -351,7 +351,7 @@ namespace Emby.Server.Implementations.SyncPlay /// The group info for the clients. public GroupInfoDto GetInfo() { - var participants = _participants.Values.Select(session => session.Session.UserName).Distinct().ToList(); + var participants = _participants.Values.Select(session => session.UserName).Distinct().ToList(); return new GroupInfoDto(GroupId, GroupName, _state.Type, participants, DateTime.UtcNow); } @@ -387,9 +387,9 @@ namespace Emby.Server.Implementations.SyncPlay { IEnumerable GetTasks() { - foreach (var session in FilterSessions(from, type)) + foreach (var sessionId in FilterSessions(from.Id, type)) { - yield return _sessionManager.SendSyncPlayGroupUpdate(session, message, cancellationToken); + yield return _sessionManager.SendSyncPlayGroupUpdate(sessionId, message, cancellationToken); } } @@ -401,9 +401,9 @@ namespace Emby.Server.Implementations.SyncPlay { IEnumerable GetTasks() { - foreach (var session in FilterSessions(from, type)) + foreach (var sessionId in FilterSessions(from.Id, type)) { - yield return _sessionManager.SendSyncPlayCommand(session, message, cancellationToken); + yield return _sessionManager.SendSyncPlayCommand(sessionId, message, cancellationToken); } } diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index aee959c53cc..3ebdbcf9ae3 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.SyncPlay _logger.LogWarning("Session {SessionId} tried to join group {GroupId} that does not exist.", session.Id, request.GroupId); var error = new GroupUpdate(Guid.Empty, GroupUpdateType.GroupDoesNotExist, string.Empty); - _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -170,7 +170,7 @@ namespace Emby.Server.Implementations.SyncPlay _logger.LogWarning("Session {SessionId} tried to join group {GroupId} but does not have access to some content of the playing queue.", session.Id, group.GroupId.ToString()); var error = new GroupUpdate(group.GroupId, GroupUpdateType.LibraryAccessDenied, string.Empty); - _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -247,7 +247,7 @@ namespace Emby.Server.Implementations.SyncPlay _logger.LogWarning("Session {SessionId} does not belong to any group.", session.Id); var error = new GroupUpdate(Guid.Empty, GroupUpdateType.NotInGroup, string.Empty); - _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } } @@ -324,7 +324,7 @@ namespace Emby.Server.Implementations.SyncPlay _logger.LogWarning("Session {SessionId} does not belong to any group.", session.Id); var error = new GroupUpdate(Guid.Empty, GroupUpdateType.NotInGroup, string.Empty); - _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); } } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 6c06dcad588..d22c9c6cfef 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -156,20 +156,20 @@ namespace MediaBrowser.Controller.Session /// /// Sends a SyncPlayCommand to a session. /// - /// The session. + /// The identifier of the session. /// The command. /// The cancellation token. /// Task. - Task SendSyncPlayCommand(SessionInfo session, SendCommand command, CancellationToken cancellationToken); + Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken); /// /// Sends a SyncPlayGroupUpdate to a session. /// - /// The session. + /// The identifier of the session. /// The group update. /// The cancellation token. /// Task. - Task SendSyncPlayGroupUpdate(SessionInfo session, GroupUpdate command, CancellationToken cancellationToken); + Task SendSyncPlayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken); /// /// Sends the browse command. diff --git a/MediaBrowser.Controller/SyncPlay/GroupMember.cs b/MediaBrowser.Controller/SyncPlay/GroupMember.cs index 5fb982e85ad..361c91d530a 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupMember.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupMember.cs @@ -1,3 +1,4 @@ +using System; using MediaBrowser.Controller.Session; namespace MediaBrowser.Controller.SyncPlay @@ -13,14 +14,28 @@ namespace MediaBrowser.Controller.SyncPlay /// The session. public GroupMember(SessionInfo session) { - Session = session; + SessionId = session.Id; + UserId = session.UserId; + UserName = session.UserName; } /// - /// Gets the session. + /// Gets the identifier of the session. /// - /// The session. - public SessionInfo Session { get; } + /// The session identifier. + public string SessionId { get; } + + /// + /// Gets the identifier of the user. + /// + /// The user identifier. + public Guid UserId { get; } + + /// + /// Gets the username. + /// + /// The username. + public string UserName { get; } /// /// Gets or sets the ping, in milliseconds. From 776ce7c660a6d6bf975766378d6db7124f4ac232 Mon Sep 17 00:00:00 2001 From: Ionut Andrei Oanca Date: Mon, 29 Mar 2021 11:29:48 +0200 Subject: [PATCH 014/278] Send playing item status in SyncPlay group update --- Emby.Server.Implementations/SyncPlay/Group.cs | 4 +++- MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/SyncPlay/Group.cs b/Emby.Server.Implementations/SyncPlay/Group.cs index 2fd971c9d75..40178dcd24e 100644 --- a/Emby.Server.Implementations/SyncPlay/Group.cs +++ b/Emby.Server.Implementations/SyncPlay/Group.cs @@ -647,8 +647,9 @@ namespace Emby.Server.Implementations.SyncPlay public PlayQueueUpdate GetPlayQueueUpdate(PlayQueueUpdateReason reason) { var startPositionTicks = PositionTicks; + var isPlaying = _state.Type.Equals(GroupStateType.Playing); - if (_state.Type.Equals(GroupStateType.Playing)) + if (isPlaying) { var currentTime = DateTime.UtcNow; var elapsedTime = currentTime - LastActivity; @@ -667,6 +668,7 @@ namespace Emby.Server.Implementations.SyncPlay PlayQueue.GetPlaylist(), PlayQueue.PlayingItemIndex, startPositionTicks, + isPlaying, PlayQueue.ShuffleMode, PlayQueue.RepeatMode); } diff --git a/MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs b/MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs index a851229f742..cce99c77d57 100644 --- a/MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs +++ b/MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs @@ -16,15 +16,17 @@ namespace MediaBrowser.Model.SyncPlay /// The playlist. /// The playing item index in the playlist. /// The start position ticks. + /// The playing item status. /// The shuffle mode. /// The repeat mode. - public PlayQueueUpdate(PlayQueueUpdateReason reason, DateTime lastUpdate, IReadOnlyList playlist, int playingItemIndex, long startPositionTicks, GroupShuffleMode shuffleMode, GroupRepeatMode repeatMode) + public PlayQueueUpdate(PlayQueueUpdateReason reason, DateTime lastUpdate, IReadOnlyList playlist, int playingItemIndex, long startPositionTicks, bool isPlaying, GroupShuffleMode shuffleMode, GroupRepeatMode repeatMode) { Reason = reason; LastUpdate = lastUpdate; Playlist = playlist; PlayingItemIndex = playingItemIndex; StartPositionTicks = startPositionTicks; + IsPlaying = isPlaying; ShuffleMode = shuffleMode; RepeatMode = repeatMode; } @@ -59,6 +61,12 @@ namespace MediaBrowser.Model.SyncPlay /// The start position ticks. public long StartPositionTicks { get; } + /// + /// Gets a value indicating whether the current item is playing. + /// + /// The playing item status. + public bool IsPlaying { get; } + /// /// Gets the shuffle mode. /// From 5fb7557763566ef4345aa563c4c1b6d81ce3ff57 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 10 Apr 2021 12:03:52 +0100 Subject: [PATCH 015/278] Network Access Policy --- Jellyfin.Api/Auth/BaseAuthorizationHandler.cs | 12 ++++- .../NetworkAccessHandler.cs | 48 +++++++++++++++++++ .../NetworkAccessRequirement.cs | 11 +++++ Jellyfin.Api/Constants/Policies.cs | 5 ++ .../ApiServiceCollectionExtensions.cs | 11 ++++- 5 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessHandler.cs create mode 100644 Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessRequirement.cs diff --git a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs index 7d68aecf99c..456f45d97bc 100644 --- a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs +++ b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs @@ -1,4 +1,4 @@ -using System.Security.Claims; +using System.Security.Claims; using Jellyfin.Api.Helpers; using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; @@ -36,6 +36,16 @@ namespace Jellyfin.Api.Auth _httpContextAccessor = httpContextAccessor; } + /// + /// Gets a value indicating being used. + /// + protected INetworkManager NetworkManager => _networkManager; + + /// + /// Gets a value indicating the being used. + /// + protected IHttpContextAccessor HttpContextAccessor => _httpContextAccessor; + /// /// Validate authenticated claims. /// diff --git a/Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessHandler.cs b/Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessHandler.cs new file mode 100644 index 00000000000..e6b33f56500 --- /dev/null +++ b/Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessHandler.cs @@ -0,0 +1,48 @@ +using System.Threading.Tasks; +using Jellyfin.Api.Auth; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Library; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Auth.NetworkAccessPolicy +{ + /// + /// Local access handler. + /// + public class NetworkAccessHandler : BaseAuthorizationHandler + { + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public NetworkAccessHandler( + IUserManager userManager, + INetworkManager networkManager, + IHttpContextAccessor httpContextAccessor) + : base(userManager, networkManager, httpContextAccessor) + { + } + + /// + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, NetworkAccessRequirement requirement) + { + var ip = HttpContextAccessor.HttpContext?.Connection.RemoteIpAddress; + + // Loopback will be on LAN, so we can accept null. + if (ip == null || NetworkManager.IsInLocalNetwork(ip)) + { + context.Succeed(requirement); + } + else + { + context.Fail(); + } + + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessRequirement.cs b/Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessRequirement.cs new file mode 100644 index 00000000000..b5431501b23 --- /dev/null +++ b/Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessRequirement.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.NetworkAccessPolicy +{ + /// + /// The local network authorization requirement. + /// + public class NetworkAccessRequirement : IAuthorizationRequirement + { + } +} diff --git a/Jellyfin.Api/Constants/Policies.cs b/Jellyfin.Api/Constants/Policies.cs index 632dedb3cda..d98debd0a15 100644 --- a/Jellyfin.Api/Constants/Policies.cs +++ b/Jellyfin.Api/Constants/Policies.cs @@ -45,6 +45,11 @@ namespace Jellyfin.Api.Constants /// public const string LocalAccessOrRequiresElevation = "LocalAccessOrRequiresElevation"; + /// + /// Policy name for requiring local LAN access. + /// + public const string NetworkAccessPolicy = "NetworkAccessPolicy"; + /// /// Policy name for escaping schedule controls or requiring first time setup. /// diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 2b34370a09d..48afc2b8c1c 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -15,6 +15,7 @@ using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; using Jellyfin.Api.Auth.IgnoreParentalControlPolicy; using Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy; using Jellyfin.Api.Auth.LocalAccessPolicy; +using Jellyfin.Api.Auth.NetworkAccessPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Auth.SyncPlayAccessPolicy; using Jellyfin.Api.Constants; @@ -61,6 +62,7 @@ namespace Jellyfin.Server.Extensions serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); @@ -113,7 +115,7 @@ namespace Jellyfin.Server.Extensions policy => { policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new LocalAccessRequirement()); + policy.AddRequirements(new NetworkAccessRequirement()); }); options.AddPolicy( Policies.LocalAccessOrRequiresElevation, @@ -157,6 +159,13 @@ namespace Jellyfin.Server.Extensions policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); policy.AddRequirements(new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.IsInGroup)); }); + options.AddPolicy( + Policies.NetworkAccessPolicy, + policy => + { + policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); + policy.AddRequirements(new NetworkAccessRequirement()); + }); }); } From cf3aff93f219ab274e0cdfc4a7edb435f85aacd4 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 10 Apr 2021 12:09:24 +0100 Subject: [PATCH 016/278] revert change error --- Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 48afc2b8c1c..8570a4a96c7 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -115,7 +115,7 @@ namespace Jellyfin.Server.Extensions policy => { policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new NetworkAccessRequirement()); + policy.AddRequirements(new LocalAccessRequirement()); }); options.AddPolicy( Policies.LocalAccessOrRequiresElevation, From 0db5df86dffd78f95263af4413fab4e321c7d2a0 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 10 Apr 2021 18:49:18 +0100 Subject: [PATCH 017/278] Update Jellyfin.Api/Constants/Policies.cs Co-authored-by: Cody Robibero --- Jellyfin.Api/Constants/Policies.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Constants/Policies.cs b/Jellyfin.Api/Constants/Policies.cs index d98debd0a15..b176bfa32cb 100644 --- a/Jellyfin.Api/Constants/Policies.cs +++ b/Jellyfin.Api/Constants/Policies.cs @@ -48,7 +48,7 @@ namespace Jellyfin.Api.Constants /// /// Policy name for requiring local LAN access. /// - public const string NetworkAccessPolicy = "NetworkAccessPolicy"; + public const string LocalNetworkAccessPolicy = "LocalNetworkAccessPolicy"; /// /// Policy name for escaping schedule controls or requiring first time setup. From af027b62835a64b439b2f66bf50205aea9b47da0 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 10 Apr 2021 19:23:27 +0100 Subject: [PATCH 018/278] Fixed after accepting suggestion. --- Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 8570a4a96c7..075a3ac15ed 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -160,7 +160,7 @@ namespace Jellyfin.Server.Extensions policy.AddRequirements(new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.IsInGroup)); }); options.AddPolicy( - Policies.NetworkAccessPolicy, + Policies.LocalNetworkAccessPolicy, policy => { policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); From fb7587dd84784afb7c64aadf1fc2d0bab5251814 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sun, 11 Apr 2021 17:17:05 +0100 Subject: [PATCH 019/278] Renamed --- .../LocalNetworkAccessHandler.cs} | 12 +++++------- .../LocalNetworkAccessRequirement.cs} | 4 ++-- .../Extensions/ApiServiceCollectionExtensions.cs | 6 +++--- 3 files changed, 10 insertions(+), 12 deletions(-) rename Jellyfin.Api/Auth/{NetworkAccessPolicy/NetworkAccessHandler.cs => LocalNetworkAccessPolicy/LocalNetworkAccessHandler.cs} (77%) rename Jellyfin.Api/Auth/{NetworkAccessPolicy/NetworkAccessRequirement.cs => LocalNetworkAccessPolicy/LocalNetworkAccessRequirement.cs} (53%) diff --git a/Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessHandler.cs b/Jellyfin.Api/Auth/LocalNetworkAccessPolicy/LocalNetworkAccessHandler.cs similarity index 77% rename from Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessHandler.cs rename to Jellyfin.Api/Auth/LocalNetworkAccessPolicy/LocalNetworkAccessHandler.cs index e6b33f56500..d691c5594c7 100644 --- a/Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessHandler.cs +++ b/Jellyfin.Api/Auth/LocalNetworkAccessPolicy/LocalNetworkAccessHandler.cs @@ -1,25 +1,23 @@ using System.Threading.Tasks; -using Jellyfin.Api.Auth; -using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; -namespace Jellyfin.Api.Auth.NetworkAccessPolicy +namespace Jellyfin.Api.Auth.LocalNetworkAccessPolicy { /// /// Local access handler. /// - public class NetworkAccessHandler : BaseAuthorizationHandler + public class LocalNetworkAccessHandler : BaseAuthorizationHandler { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - public NetworkAccessHandler( + public LocalNetworkAccessHandler( IUserManager userManager, INetworkManager networkManager, IHttpContextAccessor httpContextAccessor) @@ -28,7 +26,7 @@ namespace Jellyfin.Api.Auth.NetworkAccessPolicy } /// - protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, NetworkAccessRequirement requirement) + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LocalNetworkAccessRequirement requirement) { var ip = HttpContextAccessor.HttpContext?.Connection.RemoteIpAddress; diff --git a/Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessRequirement.cs b/Jellyfin.Api/Auth/LocalNetworkAccessPolicy/LocalNetworkAccessRequirement.cs similarity index 53% rename from Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessRequirement.cs rename to Jellyfin.Api/Auth/LocalNetworkAccessPolicy/LocalNetworkAccessRequirement.cs index b5431501b23..29ca9c355cc 100644 --- a/Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessRequirement.cs +++ b/Jellyfin.Api/Auth/LocalNetworkAccessPolicy/LocalNetworkAccessRequirement.cs @@ -1,11 +1,11 @@ using Microsoft.AspNetCore.Authorization; -namespace Jellyfin.Api.Auth.NetworkAccessPolicy +namespace Jellyfin.Api.Auth.LocalNetworkAccessPolicy { /// /// The local network authorization requirement. /// - public class NetworkAccessRequirement : IAuthorizationRequirement + public class LocalNetworkAccessRequirement : IAuthorizationRequirement { } } diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 075a3ac15ed..3a34c501df2 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -15,7 +15,7 @@ using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; using Jellyfin.Api.Auth.IgnoreParentalControlPolicy; using Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy; using Jellyfin.Api.Auth.LocalAccessPolicy; -using Jellyfin.Api.Auth.NetworkAccessPolicy; +using Jellyfin.Api.Auth.LocalNetworkAccessPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Auth.SyncPlayAccessPolicy; using Jellyfin.Api.Constants; @@ -62,7 +62,7 @@ namespace Jellyfin.Server.Extensions serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); @@ -164,7 +164,7 @@ namespace Jellyfin.Server.Extensions policy => { policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new NetworkAccessRequirement()); + policy.AddRequirements(new LocalNetworkAccessRequirement()); }); }); } From d0537a3271ca9294dce1e86af290e2109ba5e15f Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 21 May 2021 15:07:35 +0100 Subject: [PATCH 020/278] Set isRoot to true --- MediaBrowser.Controller/Entities/UserRootFolder.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 7f7224ae078..30e08184506 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -21,6 +21,15 @@ namespace MediaBrowser.Controller.Entities { private List _childrenIds = null; private readonly object _childIdsLock = new object(); + + /// + /// Initializes a new instance of the class. + /// + public UserRootFolder() + { + IsRoot = true; + } + protected override List LoadChildren() { lock (_childIdsLock) From 4e9570598b06b9301632cf90dbfaf71c075334cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Jun 2021 14:17:50 +0000 Subject: [PATCH 021/278] Bump cirrus-actions/rebase from 1.4 to 1.5 Bumps [cirrus-actions/rebase](https://github.com/cirrus-actions/rebase) from 1.4 to 1.5. - [Release notes](https://github.com/cirrus-actions/rebase/releases) - [Commits](https://github.com/cirrus-actions/rebase/compare/1.4...1.5) Signed-off-by: dependabot[bot] --- .github/workflows/commands.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index e0b91ecee6d..af4d8beb934 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -29,7 +29,7 @@ jobs: fetch-depth: 0 - name: Automatic Rebase - uses: cirrus-actions/rebase@1.4 + uses: cirrus-actions/rebase@1.5 env: GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }} From 8a65a6dfc30116630759ba305637451328511413 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 3 Jul 2021 02:50:19 +0200 Subject: [PATCH 022/278] Use artist backdrop for generated library image --- Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 3 +++ MediaBrowser.Controller/Entities/BaseItem.cs | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index 09a37023851..3f66929617d 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -149,6 +149,9 @@ namespace Jellyfin.Drawing.Skia canvas.DrawText(libraryName, width / 2f, (height / 2f) + (textPaint.FontMetrics.XHeight / 2), textPaint); + paintColor.Dispose(); + textPaint.Dispose(); + return bitmap; } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index e5be5421a2f..42e795f69b0 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -19,6 +19,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; @@ -2386,6 +2387,17 @@ namespace MediaBrowser.Controller.Entities }; } + // Music albums usually don't have dedicated backdrops, so return one from the artist instead + if (GetType() == typeof(MusicAlbum) && imageType == ImageType.Backdrop) + { + var artist = FindParent(); + + if (artist != null) + { + return artist.GetImages(imageType).ElementAtOrDefault(imageIndex); + } + } + return GetImages(imageType) .ElementAtOrDefault(imageIndex); } From 3b8947aba6937cb9b38f4e5769816dbf674e0dd6 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Mon, 5 Jul 2021 19:41:10 +0200 Subject: [PATCH 023/278] Add using keywords to non-disposed objects in BuildThumbCollageBitmap --- Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index 3f66929617d..d1cc2255d76 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -112,7 +112,7 @@ namespace Jellyfin.Drawing.Skia canvas.DrawImage(residedBackdrop, 0, 0); // draw shadow rectangle - var paintColor = new SKPaint + using var paintColor = new SKPaint { Color = SKColors.Black.WithAlpha(0x78), Style = SKPaintStyle.Fill @@ -130,7 +130,7 @@ namespace Jellyfin.Drawing.Skia } // draw library name - var textPaint = new SKPaint + using var textPaint = new SKPaint { Color = SKColors.White, Style = SKPaintStyle.Fill, @@ -149,9 +149,6 @@ namespace Jellyfin.Drawing.Skia canvas.DrawText(libraryName, width / 2f, (height / 2f) + (textPaint.FontMetrics.XHeight / 2), textPaint); - paintColor.Dispose(); - textPaint.Dispose(); - return bitmap; } From 2cd1c70e556e682a421121bdb01be50eee84194e Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Mon, 12 Jul 2021 10:06:13 +0200 Subject: [PATCH 024/278] Add OpenAPI workflow --- .github/workflows/openapi.yml | 119 ++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 .github/workflows/openapi.yml diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml new file mode 100644 index 00000000000..8bef8fa6545 --- /dev/null +++ b/.github/workflows/openapi.yml @@ -0,0 +1,119 @@ +name: OpenAPI +on: + push: + branches: + - master + pull_request: + +jobs: + openapi-head: + name: OpenAPI - HEAD + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '5.0.x' + - name: Generate openapi.json + run: dotnet test -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests" + - name: Upload openapi.json + uses: actions/upload-artifact@v2 + with: + name: openapi-head + retention-days: 14 + if-no-files-found: error + path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net5.0/openapi.json + + openapi-base: + name: OpenAPI - BASE + if: ${{ github.base_ref != '' }} + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + ref: ${{ github.base_ref }} + - name: Setup .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '5.0.x' + - name: Generate openapi.json + run: dotnet test -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests" + - name: Upload openapi.json + uses: actions/upload-artifact@v2 + with: + name: openapi-base + retention-days: 14 + if-no-files-found: error + path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net5.0/openapi.json + + openapi-diff: + name: OpenAPI - Difference + if: ${{ github.event_name == 'pull_request' }} + runs-on: ubuntu-latest + needs: + - openapi-head + - openapi-base + steps: + - name: Download openapi-head + uses: actions/download-artifact@v2 + with: + name: openapi-head + path: openapi-head + - name: Download openapi-base + uses: actions/download-artifact@v2 + with: + name: openapi-base + path: openapi-base + - name: Workaround openapi-diff issue + run: | + sed -i 's/"allOf"/"oneOf"/g' openapi-head/openapi.json + sed -i 's/"allOf"/"oneOf"/g' openapi-base/openapi.json + - name: Calculate OpenAPI difference + uses: docker://openapitools/openapi-diff + continue-on-error: true + with: + args: --fail-on-changed --markdown openapi-changes.md openapi-base/openapi.json openapi-head/openapi.json + - id: read-diff + name: Read openapi-diff output + run: | + body=$(cat openapi-changes.md) + body="${body//'%'/'%25'}" + body="${body//$'\n'/'%0A'}" + body="${body//$'\r'/'%0D'}" + echo ::set-output name=body::$body + - name: Find difference comment + uses: peter-evans/find-comment@v1 + id: find-comment + with: + issue-number: ${{ github.event.pull_request.number }} + direction: last + body-includes: openapi-diff-workflow-comment + - name: Reply or edit difference comment (changed) + uses: peter-evans/create-or-update-comment@v1.4.5 + if: ${{ steps.read-diff.outputs.body != '' }} + with: + issue-number: ${{ github.event.pull_request.number }} + comment-id: ${{ steps.find-comment.outputs.comment-id }} + edit-mode: replace + body: | + +
+ Changes in OpenAPI specification found. Expand to see details. + + ${{ steps.read-diff.outputs.body }} + +
+ - name: Edit difference comment (unchanged) + uses: peter-evans/create-or-update-comment@v1.4.5 + if: ${{ steps.read-diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }} + with: + issue-number: ${{ github.event.pull_request.number }} + comment-id: ${{ steps.find-comment.outputs.comment-id }} + edit-mode: replace + body: | + + + No changes to OpenAPI specification found. See history of this comment for previous changes. From 0c9b64de4be633f2a4f47244996c8a25bbb0db45 Mon Sep 17 00:00:00 2001 From: joey Date: Tue, 3 Aug 2021 13:20:36 +0800 Subject: [PATCH 025/278] optimize episode parser --- Emby.Naming/Common/NamingOptions.cs | 2 ++ tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 5f125eb4f14..ed2b2bf7080 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -250,6 +250,8 @@ namespace Emby.Naming.Common }, // new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"), + // + new EpisodeExpression(@"[^\\/]*?()\.?[Ee]([0-9]+)\.([^\\/]*)$"), new EpisodeExpression("(?[0-9]{4})[\\.-](?[0-9]{2})[\\.-](?[0-9]{2})", true) { DateTimeFormats = new[] diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs index 2873f616139..1e7fedb36fa 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs @@ -71,9 +71,9 @@ namespace Jellyfin.Naming.Tests.TV [InlineData("Season 1/seriesname 05.mkv", 5)] // no hyphen between series name and episode number [InlineData("[BBT-RMX] Ranma ½ - 154 [50AC421A].mkv", 154)] // hyphens in the pre-name info, triple digit episode number [InlineData("Season 2/Episode 21 - 94 Meetings.mp4", 21)] // Title starts with a number + [InlineData("/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv", 7)] // [InlineData("Case Closed (1996-2007)/Case Closed - 317.mkv", 317)] // triple digit episode number // TODO: [InlineData("Season 2/16 12 Some Title.avi", 16)] - // TODO: [InlineData("/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv", 7)] // TODO: [InlineData("Season 4/Uchuu.Senkan.Yamato.2199.E03.avi", 3)] // TODO: [InlineData("Season 2/7 12 Angry Men.avi", 7)] // TODO: [InlineData("Season 02/02x03x04x15 - Ep Name.mp4", 2)] From 26f8b501e77b7bd9a73028637e82de2f2605dd3a Mon Sep 17 00:00:00 2001 From: sushilicious <*> Date: Tue, 3 Aug 2021 13:46:56 -0700 Subject: [PATCH 026/278] Made CleanStringParser more robust Now it can handle [...] at beginning of string --- Emby.Naming/Common/NamingOptions.cs | 7 ++- Emby.Naming/Video/CleanStringParser.cs | 44 +++++++++++++++---- .../Video/CleanStringTests.cs | 8 +++- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 5f125eb4f14..eb95c9b9c33 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -137,8 +137,11 @@ namespace Emby.Naming.Common CleanStrings = new[] { - @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|blu-ray|x264|x265|h264|h265|xvid|xvidvd|xxx|www.www|AAC|DTS|\[.*\])([ _\,\.\(\)\[\]\-]|$)", - @"(\[.*\])" + @"^\s*(?.+?)[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|blu-ray|x264|x265|h264|h265|xvid|xvidvd|xxx|www.www|AAC|DTS|\[.*\])([ _\,\.\(\)\[\]\-]|$)", + @"^(?.+?)(\[.*\])", + @"^\s*(?.+?)\WE\d+(-|~)E?\d+(\W|$)", + @"^\s*\[[^\]]+\](?!\.\w+$)\s*(?.+)", + @"^\s*(?.+?)\s+-\s+\d+\s*$" }; SubtitleFileExtensions = new[] diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs index 4eef3ebc5eb..05180957050 100644 --- a/Emby.Naming/Video/CleanStringParser.cs +++ b/Emby.Naming/Video/CleanStringParser.cs @@ -25,26 +25,54 @@ namespace Emby.Naming.Video return false; } - var len = expressions.Count; - for (int i = 0; i < len; i++) + // Iteratively remove extra cruft until we're left with the string + // we want. + newName = ReadOnlySpan.Empty; + const int maxTries = 100; // This is just a precautionary + // measure. Should not be neccesary. + var loopCounter = 0; + for (; loopCounter < maxTries; loopCounter++) { - if (TryClean(name, expressions[i], out newName)) + bool cleaned = false; + var len = expressions.Count; + for (int i = 0; i < len; i++) { - return true; + if (TryClean(name, expressions[i], out newName)) + { + cleaned = true; + name = newName.ToString(); + break; + } + } + + if (!cleaned) + { + break; } } - newName = ReadOnlySpan.Empty; - return false; + if (loopCounter > 0) + { + newName = name.AsSpan(); + } + + return newName != ReadOnlySpan.Empty; } private static bool TryClean(string name, Regex expression, out ReadOnlySpan newName) { var match = expression.Match(name); int index = match.Index; - if (match.Success && index != 0) + if (match.Success) { - newName = name.AsSpan().Slice(0, match.Index); + var found = match.Groups.TryGetValue("cleaned", out var cleaned); + if (!found || cleaned == null) + { + newName = ReadOnlySpan.Empty; + return false; + } + + newName = name.AsSpan().Slice(cleaned.Index, cleaned.Length); return true; } diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs index fb050cf5a74..1d51e7ca591 100644 --- a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using Emby.Naming.Common; using Emby.Naming.Video; using Xunit; @@ -23,6 +23,12 @@ namespace Jellyfin.Naming.Tests.Video [InlineData("Crouching.Tiger.Hidden.Dragon.BDrip.mkv", "Crouching.Tiger.Hidden.Dragon")] [InlineData("Crouching.Tiger.Hidden.Dragon.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")] [InlineData("Crouching.Tiger.Hidden.Dragon.4K.UltraHD.HDR.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")] + [InlineData("[HorribleSubs] Made in Abyss - 13 [720p].mkv", "Made in Abyss")] + [InlineData("[Tsundere] Kore wa Zombie Desu ka of the Dead [BDRip h264 1920x1080 FLAC]", "Kore wa Zombie Desu ka of the Dead")] + [InlineData("[Erai-raws] Jujutsu Kaisen - 03 [720p][Multiple Subtitle].mkv", "Jujutsu Kaisen")] + [InlineData("[OCN] 애타는 로맨스 720p-NEXT", "애타는 로맨스")] + [InlineData("[tvN] 혼술남녀.E01-E16.720p-NEXT", "혼술남녀")] + [InlineData("[tvN] 연애말고 결혼 E01~E16 END HDTV.H264.720p-WITH", "연애말고 결혼")] // FIXME: [InlineData("After The Sunset - [0004].mkv", "After The Sunset")] public void CleanStringTest_NeedsCleaning_Success(string input, string expectedName) { From b2a10609af703b1c513570d6c397ff22e0214bb0 Mon Sep 17 00:00:00 2001 From: sushilicious <*> Date: Wed, 4 Aug 2021 23:25:54 -0700 Subject: [PATCH 027/278] Made CleanStringParser go through regexps only once --- Emby.Naming/Common/NamingOptions.cs | 4 ++-- Emby.Naming/Video/CleanStringParser.cs | 33 ++++++-------------------- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index eb95c9b9c33..a745532c4f1 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -139,9 +139,9 @@ namespace Emby.Naming.Common { @"^\s*(?.+?)[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|blu-ray|x264|x265|h264|h265|xvid|xvidvd|xxx|www.www|AAC|DTS|\[.*\])([ _\,\.\(\)\[\]\-]|$)", @"^(?.+?)(\[.*\])", - @"^\s*(?.+?)\WE\d+(-|~)E?\d+(\W|$)", + @"^\s*(?.+?)\WE[0-9]+(-|~)E?[0-9]+(\W|$)", @"^\s*\[[^\]]+\](?!\.\w+$)\s*(?.+)", - @"^\s*(?.+?)\s+-\s+\d+\s*$" + @"^\s*(?.+?)\s+-\s+[0-9]+\s*$" }; SubtitleFileExtensions = new[] diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs index 05180957050..63a5953578b 100644 --- a/Emby.Naming/Video/CleanStringParser.cs +++ b/Emby.Naming/Video/CleanStringParser.cs @@ -25,37 +25,18 @@ namespace Emby.Naming.Video return false; } - // Iteratively remove extra cruft until we're left with the string - // we want. - newName = ReadOnlySpan.Empty; - const int maxTries = 100; // This is just a precautionary - // measure. Should not be neccesary. - var loopCounter = 0; - for (; loopCounter < maxTries; loopCounter++) + // Iteratively apply the regexps to clean the string. + bool cleaned = false; + for (int i = 0; i < expressions.Count; i++) { - bool cleaned = false; - var len = expressions.Count; - for (int i = 0; i < len; i++) + if (TryClean(name, expressions[i], out newName)) { - if (TryClean(name, expressions[i], out newName)) - { - cleaned = true; - name = newName.ToString(); - break; - } - } - - if (!cleaned) - { - break; + cleaned = true; + name = newName.ToString(); } } - if (loopCounter > 0) - { - newName = name.AsSpan(); - } - + newName = cleaned ? name.AsSpan() : ReadOnlySpan.Empty; return newName != ReadOnlySpan.Empty; } From 6bc7d78f6fbef9e47731a4991ca72d3ba4c23bce Mon Sep 17 00:00:00 2001 From: sushilicious <40350682+sushilicious@users.noreply.github.com> Date: Thu, 5 Aug 2021 13:09:42 -0700 Subject: [PATCH 028/278] Update Emby.Naming/Video/CleanStringParser.cs Cleaned up code a bit Co-authored-by: Claus Vium --- Emby.Naming/Video/CleanStringParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs index 63a5953578b..99cb289a25a 100644 --- a/Emby.Naming/Video/CleanStringParser.cs +++ b/Emby.Naming/Video/CleanStringParser.cs @@ -37,7 +37,7 @@ namespace Emby.Naming.Video } newName = cleaned ? name.AsSpan() : ReadOnlySpan.Empty; - return newName != ReadOnlySpan.Empty; + return cleaned; } private static bool TryClean(string name, Regex expression, out ReadOnlySpan newName) From 95988ce33dccd36cb5d4e82a83ef8e4b3bc2382b Mon Sep 17 00:00:00 2001 From: Deathspike Date: Tue, 17 Aug 2021 20:10:15 +0200 Subject: [PATCH 029/278] Fix embedded subtitles taking priority over external ones --- .../Library/MediaStreamSelector.cs | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index b833122ea02..71e3404d07e 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -57,14 +57,18 @@ namespace Emby.Server.Implementations.Library { // Prefer embedded metadata over smart logic - stream = streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ?? - streams.FirstOrDefault(s => s.IsForced) ?? - streams.FirstOrDefault(s => s.IsDefault); + stream = streams.FirstOrDefault(s => s.IsExternal && s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ?? + streams.FirstOrDefault(s => s.IsExternal && s.IsForced) ?? + streams.FirstOrDefault(s => s.IsExternal && s.IsDefault) ?? + streams.FirstOrDefault(s => s.IsExternal) ?? + streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ?? + streams.FirstOrDefault(s => s.IsForced) ?? + streams.FirstOrDefault(s => s.IsDefault); // if the audio language is not understood by the user, load their preferred subs, if there are any if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase)) { - stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)); + stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)); } } else if (mode == SubtitlePlaybackMode.Smart) @@ -74,24 +78,30 @@ namespace Emby.Server.Implementations.Library // if the audio language is not understood by the user, load their preferred subs, if there are any if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase)) { - stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ?? - streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)); + stream = streams.FirstOrDefault(s => s.IsExternal && !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ?? + streams.FirstOrDefault(s => s.IsExternal && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ?? + streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ?? + streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)); } } else if (mode == SubtitlePlaybackMode.Always) { // always load the most suitable full subtitles - stream = streams.FirstOrDefault(s => !s.IsForced); + stream = streams.FirstOrDefault(s => s.IsExternal && !s.IsForced) ?? + streams.FirstOrDefault(s => !s.IsForced); } else if (mode == SubtitlePlaybackMode.OnlyForced) { // always load the most suitable full subtitles - stream = streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ?? - streams.FirstOrDefault(s => s.IsForced); + stream = streams.FirstOrDefault(s => s.IsExternal && s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ?? + streams.FirstOrDefault(s => s.IsExternal && s.IsForced) ?? + streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ?? + streams.FirstOrDefault(s => s.IsForced); } // load forced subs if we have found no suitable full subtitles - stream ??= streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)); + stream ??= streams.FirstOrDefault(s => s.IsExternal && s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ?? + streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)); if (stream != null) { From 7027e2feb2defb3f1647f4cb829397a44661bd1f Mon Sep 17 00:00:00 2001 From: Izumiko Date: Sat, 21 Aug 2021 20:05:25 +0800 Subject: [PATCH 030/278] Add 'includeAdult' option for TheMovieDb --- .../MediaBrowser.Providers.csproj | 2 + .../Tmdb/Configuration/PluginConfiguration.cs | 15 +++++ .../Plugins/Tmdb/Configuration/config.html | 52 +++++++++++++++++ MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs | 58 +++++++++++++++++++ .../Plugins/Tmdb/TmdbClientManager.cs | 6 +- 5 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 3d866cdc2bd..8e2a901aedb 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -49,5 +49,7 @@ + + diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs new file mode 100644 index 00000000000..907f0160d63 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs @@ -0,0 +1,15 @@ +using MediaBrowser.Model.Plugins; + +namespace MediaBrowser.Providers.Plugins.Tmdb +{ + /// + /// Plugin configuration class for TMDb library. + /// + public class PluginConfiguration : BasePluginConfiguration + { + /// + /// Gets or sets a value indicating whether include adult content when searching with TMDb. + /// + public bool IncludeAdult { get; set; } + } +} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html new file mode 100644 index 00000000000..6f42549d7d3 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html @@ -0,0 +1,52 @@ + + + + TMDb + + +
+
+
+
+ +
+
+ +
+
+
+
+ +
+ + diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs b/MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs new file mode 100644 index 00000000000..ea81eb96e09 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; + +namespace MediaBrowser.Providers.Plugins.Tmdb +{ + /// + /// Plugin class for the TMDb library. + /// + public class Plugin : BasePlugin, IHasWebPages + { + /// + /// Initializes a new instance of the class. + /// + /// application paths. + /// xml serializer. + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + + /// + /// Gets the instance of TMDb plugin. + /// + public static Plugin Instance { get; private set; } + + /// + public override Guid Id => new Guid("b8715ed1-6c47-4528-9ad3-f72deb539cd4"); + + /// + public override string Name => "TMDb"; + + /// + public override string Description => "Get metadata for movies and other video content from TheMovieDb."; + + // TODO remove when plugin removed from server. + + /// + public override string ConfigurationFileName => "Jellyfin.Plugin.Tmdb.xml"; + + /// + /// Return the plugin configuration page. + /// + /// PluginPageInfo. + public IEnumerable GetPages() + { + yield return new PluginPageInfo + { + Name = Name, + EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html" + }; + } + } +} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index 4de4bf4db6f..5bd5dd2e802 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -358,7 +358,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb await EnsureClientConfigAsync().ConfigureAwait(false); var searchResults = await _tmDbClient - .SearchTvShowAsync(name, TmdbUtils.NormalizeLanguage(language), firstAirDateYear: year, cancellationToken: cancellationToken) + .SearchTvShowAsync(name, TmdbUtils.NormalizeLanguage(language), includeAdult: Plugin.Instance.Configuration.IncludeAdult, firstAirDateYear: year, cancellationToken: cancellationToken) .ConfigureAwait(false); if (searchResults.Results.Count > 0) @@ -386,7 +386,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb await EnsureClientConfigAsync().ConfigureAwait(false); var searchResults = await _tmDbClient - .SearchPersonAsync(name, cancellationToken: cancellationToken) + .SearchPersonAsync(name, includeAdult: Plugin.Instance.Configuration.IncludeAdult, cancellationToken: cancellationToken) .ConfigureAwait(false); if (searchResults.Results.Count > 0) @@ -428,7 +428,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb await EnsureClientConfigAsync().ConfigureAwait(false); var searchResults = await _tmDbClient - .SearchMovieAsync(name, TmdbUtils.NormalizeLanguage(language), year: year, cancellationToken: cancellationToken) + .SearchMovieAsync(name, TmdbUtils.NormalizeLanguage(language), includeAdult: Plugin.Instance.Configuration.IncludeAdult, year: year, cancellationToken: cancellationToken) .ConfigureAwait(false); if (searchResults.Results.Count > 0) From 6f405dc36de4692d4fb7612415dee17dc40aef2f Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 1 Sep 2021 18:47:24 +0200 Subject: [PATCH 031/278] Clean up VideoImageProvider --- .../MediaInfo/VideoImageProvider.cs | 77 +++++++------------ 1 file changed, 29 insertions(+), 48 deletions(-) diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 453938be795..8b96205c230 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -1,3 +1,4 @@ +#nullable enable #pragma warning disable CS1591 using System; @@ -11,7 +12,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging; @@ -21,37 +21,33 @@ namespace MediaBrowser.Providers.MediaInfo { private readonly IMediaEncoder _mediaEncoder; private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - public VideoImageProvider(IMediaEncoder mediaEncoder, ILogger logger, IFileSystem fileSystem) + public VideoImageProvider(IMediaEncoder mediaEncoder, ILogger logger) { _mediaEncoder = mediaEncoder; _logger = logger; - _fileSystem = fileSystem; } + /// public string Name => "Screen Grabber"; + /// // Make sure this comes after internet image providers public int Order => 100; + /// public IEnumerable GetSupportedImages(BaseItem item) { - return new List { ImageType.Primary }; + return new[] { ImageType.Primary }; } + /// public Task GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken) { var video = (Video)item; - // No support for this - if (video.IsPlaceHolder) - { - return Task.FromResult(new DynamicImageResponse { HasImage = false }); - } - - // No support for this - if (video.VideoType == VideoType.Dvd) + // No support for these + if (video.IsPlaceHolder || video.VideoType == VideoType.Dvd) { return Task.FromResult(new DynamicImageResponse { HasImage = false }); } @@ -59,18 +55,21 @@ namespace MediaBrowser.Providers.MediaInfo // Can't extract if we didn't find a video stream in the file if (!video.DefaultVideoStreamIndex.HasValue) { - _logger.LogInformation("Skipping image extraction due to missing DefaultVideoStreamIndex for {0}.", video.Path ?? string.Empty); + _logger.LogInformation("Skipping image extraction due to missing DefaultVideoStreamIndex for {Path}.", video.Path ?? string.Empty); return Task.FromResult(new DynamicImageResponse { HasImage = false }); } return GetVideoImage(video, cancellationToken); } - public async Task GetVideoImage(Video item, CancellationToken cancellationToken) + private async Task GetVideoImage(Video item, CancellationToken cancellationToken) { - var protocol = item.PathProtocol ?? MediaProtocol.File; - - var inputPath = item.Path; + MediaSourceInfo mediaSource = new MediaSourceInfo + { + VideoType = item.VideoType, + IsoType = item.IsoType, + Protocol = item.PathProtocol ?? MediaProtocol.File, + }; var mediaStreams = item.GetMediaStreams(); @@ -80,41 +79,27 @@ namespace MediaBrowser.Providers.MediaInfo .Where(i => i.Type == MediaStreamType.EmbeddedImage) .ToList(); - var imageStream = imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("front", StringComparison.OrdinalIgnoreCase) != -1) ?? - imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("cover", StringComparison.OrdinalIgnoreCase) != -1) ?? - imageStreams.FirstOrDefault(); - string extractedImagePath; - if (imageStream != null) - { - MediaSourceInfo mediaSource = new MediaSourceInfo - { - VideoType = item.VideoType, - IsoType = item.IsoType, - Protocol = item.PathProtocol.Value, - }; - - extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, mediaSource, imageStream, imageStream.Index, cancellationToken).ConfigureAwait(false); - } - else + if (imageStreams.Count == 0) { // If we know the duration, grab it from 10% into the video. Otherwise just 10 seconds in. // Always use 10 seconds for dvd because our duration could be out of whack var imageOffset = item.VideoType != VideoType.Dvd && item.RunTimeTicks.HasValue && item.RunTimeTicks.Value > 0 - ? TimeSpan.FromTicks(Convert.ToInt64(item.RunTimeTicks.Value * .1)) + ? TimeSpan.FromTicks(item.RunTimeTicks.Value / 10) : TimeSpan.FromSeconds(10); var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - var mediaSource = new MediaSourceInfo - { - VideoType = item.VideoType, - IsoType = item.IsoType, - Protocol = item.PathProtocol.Value, - }; + extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, videoStream, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); + } + else + { + var imageStream = imageStreams.Find(i => (i.Comment ?? string.Empty).Contains("front", StringComparison.OrdinalIgnoreCase)) + ?? imageStreams.Find(i => (i.Comment ?? string.Empty).Contains("cover", StringComparison.OrdinalIgnoreCase)) + ?? imageStreams[0]; - extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, mediaSource, videoStream, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); + extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, cancellationToken).ConfigureAwait(false); } return new DynamicImageResponse @@ -126,6 +111,7 @@ namespace MediaBrowser.Providers.MediaInfo }; } + /// public bool Supports(BaseItem item) { if (item.IsShortcut) @@ -138,12 +124,7 @@ namespace MediaBrowser.Providers.MediaInfo return false; } - if (item is Video video && !video.IsPlaceHolder && video.IsCompleteMedia) - { - return true; - } - - return false; + return item is Video video && !video.IsPlaceHolder && video.IsCompleteMedia; } } } From e3dac4fda2033801085eb7086a3a534c473a00a0 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 12 Jun 2021 22:20:35 +0200 Subject: [PATCH 032/278] Use async FileStreams where it makes sense --- Emby.Dlna/DlnaManager.cs | 2 +- Emby.Drawing/ImageProcessor.cs | 2 +- .../Channels/ChannelManager.cs | 4 +- .../IO/ManagedFileSystem.cs | 2 +- .../Library/LiveStreamHelper.cs | 5 +- .../Library/MediaSourceManager.cs | 2 +- .../LiveTv/EmbyTV/DirectRecorder.cs | 4 +- .../LiveTv/EmbyTV/EncodedRecorder.cs | 2 +- .../LiveTv/Listings/XmlTvListingsProvider.cs | 2 +- .../LiveTv/TunerHosts/BaseTunerHost.cs | 4 +- .../LiveTv/TunerHosts/LiveStream.cs | 4 +- .../LiveTv/TunerHosts/M3uParser.cs | 3 +- .../LiveTv/TunerHosts/SharedHttpStream.cs | 58 ++++++++++--------- .../Plugins/PluginManager.cs | 3 +- .../Serialization/MyXmlSerializer.cs | 2 +- .../Controllers/ImageByNameController.cs | 2 +- .../Controllers/RemoteImageController.cs | 2 +- Jellyfin.Api/Controllers/SystemController.cs | 2 +- Jellyfin.Api/Helpers/HlsHelpers.cs | 2 +- Jellyfin.Api/Helpers/ProgressiveFileCopier.cs | 3 +- Jellyfin.Api/Helpers/ProgressiveFileStream.cs | 2 +- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 2 +- .../Users/DefaultPasswordResetProvider.cs | 5 +- Jellyfin.Server/Program.cs | 3 +- .../Attachments/AttachmentExtractor.cs | 2 +- .../BdInfo/BdInfoFileInfo.cs | 6 +- .../Probing/ProbeResultNormalizer.cs | 2 +- .../Subtitles/SubtitleEncoder.cs | 8 +-- MediaBrowser.Model/IO/AsyncFile.cs | 34 +++++++++++ MediaBrowser.Providers/Manager/ImageSaver.cs | 2 +- .../Manager/ItemImageProvider.cs | 2 +- .../Manager/ProviderManager.cs | 2 +- .../AudioDb/AudioDbAlbumImageProvider.cs | 3 +- .../Plugins/AudioDb/AudioDbAlbumProvider.cs | 4 +- .../AudioDb/AudioDbArtistImageProvider.cs | 3 +- .../Plugins/AudioDb/AudioDbArtistProvider.cs | 4 +- .../Plugins/Omdb/OmdbProvider.cs | 22 ++----- .../Studios/StudiosImageProvider.cs | 2 +- .../Subtitles/SubtitleManager.cs | 2 +- .../FFprobeParserTests.cs | 3 +- 40 files changed, 125 insertions(+), 98 deletions(-) create mode 100644 MediaBrowser.Model/IO/AsyncFile.cs diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index af70793ccfe..68fc80c0a07 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -366,7 +366,7 @@ namespace Emby.Dlna Directory.CreateDirectory(systemProfilesPath); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None)) + using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO)) { await stream.CopyToAsync(fileStream).ConfigureAwait(false); } diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 7d952aa23bb..0ad8bca3148 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -102,7 +102,7 @@ namespace Emby.Drawing { var file = await ProcessImage(options).ConfigureAwait(false); - using (var fileStream = new FileStream(file.Item1, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true)) + using (var fileStream = new FileStream(file.Item1, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO)) { await fileStream.CopyToAsync(toStream).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index aa54510a710..1478e931355 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -815,7 +815,7 @@ namespace Emby.Server.Implementations.Channels { if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) { - await using FileStream jsonStream = File.OpenRead(cachePath); + await using FileStream jsonStream = AsyncFile.OpenRead(cachePath); var cachedResult = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); if (cachedResult != null) { @@ -838,7 +838,7 @@ namespace Emby.Server.Implementations.Channels { if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) { - await using FileStream jsonStream = File.OpenRead(cachePath); + await using FileStream jsonStream = AsyncFile.OpenRead(cachePath); var cachedResult = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); if (cachedResult != null) { diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 7c3c7da2301..af0d88ea5ae 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -248,7 +248,7 @@ namespace Emby.Server.Implementations.IO { try { - using (Stream thisFileStream = File.OpenRead(fileInfo.FullName)) + using (Stream thisFileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read, 1)) { result.Length = thisFileStream.Length; } diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index 8062691827b..16b45161fd0 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -17,6 +17,7 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging; @@ -49,7 +50,7 @@ namespace Emby.Server.Implementations.Library { try { - await using FileStream jsonStream = File.OpenRead(cacheFilePath); + await using FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath); mediaInfo = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); // _logger.LogDebug("Found cached media info"); @@ -86,7 +87,7 @@ namespace Emby.Server.Implementations.Library if (cacheFilePath != null) { Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); - await using FileStream createStream = File.OpenWrite(cacheFilePath); + await using FileStream createStream = AsyncFile.OpenWrite(cacheFilePath); await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false); // _logger.LogDebug("Saved media info to {0}", cacheFilePath); diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 91c9e61cf35..4e0434b92a8 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -638,7 +638,7 @@ namespace Emby.Server.Implementations.Library { try { - await using FileStream jsonStream = File.OpenRead(cacheFilePath); + await using FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath); mediaInfo = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); // _logger.LogDebug("Found cached media info"); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index bb3d635d120..c5a9a92ec24 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile))); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None)) + using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO)) { onStarted(); @@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile))); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None); + await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.CopyToBufferSize, AsyncFile.UseAsyncIO); onStarted(); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index e10bc764707..d806a02959c 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV Directory.CreateDirectory(Path.GetDirectoryName(logFilePath)); // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - _logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); + _logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); await JsonSerializer.SerializeAsync(_logFileStream, mediaSource, _jsonOptions, cancellationToken).ConfigureAwait(false); await _logFileStream.WriteAsync(Encoding.UTF8.GetBytes(Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine), cancellationToken).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index ebad4eddf18..8202fab861b 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(path, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - await using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew)) + await using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.CopyToBufferSize, AsyncFile.UseAsyncIO)) { await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 5941613cf91..f87212cf336 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts try { Directory.CreateDirectory(Path.GetDirectoryName(channelCacheFile)); - await using var writeStream = File.OpenWrite(channelCacheFile); + await using var writeStream = AsyncFile.OpenWrite(channelCacheFile); await JsonSerializer.SerializeAsync(writeStream, channels, cancellationToken: cancellationToken).ConfigureAwait(false); } catch (IOException) @@ -108,7 +108,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { try { - await using var readStream = File.OpenRead(channelCacheFile); + await using var readStream = AsyncFile.OpenRead(channelCacheFile); var channels = await JsonSerializer.DeserializeAsync>(readStream, cancellationToken: cancellationToken) .ConfigureAwait(false); list.AddRange(channels); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 96a678c1d3a..2c21a4a8933 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -155,15 +155,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token); cancellationToken = linkedCancellationTokenSource.Token; - // use non-async filestream on windows along with read due to https://github.com/dotnet/corefx/issues/6039 - var allowAsync = Environment.OSVersion.Platform != PlatformID.Win32NT; - bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10; var nextFileInfo = GetNextFile(null); var nextFile = nextFileInfo.file; var isLastFile = nextFileInfo.isLastFile; + var allowAsync = AsyncFile.UseAsyncIO; while (!string.IsNullOrEmpty(nextFile)) { var emptyReadLimit = isLastFile ? EmptyReadLimit : 1; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index d28c39e213d..23071a4306f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -14,6 +14,7 @@ using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; using Microsoft.Extensions.Logging; @@ -50,7 +51,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (!info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase)) { - return File.OpenRead(info.Url); + return AsyncFile.OpenRead(info.Url); } using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index f572151b8a1..86299387770 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -129,37 +129,39 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts private Task StartStreaming(HttpResponseMessage response, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) { - return Task.Run(async () => - { - try + return Task.Run( + async () => { - Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath); - using var message = response; - await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read); - await StreamHelper.CopyToAsync( - stream, - fileStream, - IODefaults.CopyToBufferSize, - () => Resolve(openTaskCompletionSource), - cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException ex) - { - Logger.LogInformation("Copying of {0} to {1} was canceled", GetType().Name, TempFilePath); - openTaskCompletionSource.TrySetException(ex); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error copying live stream {0} to {1}.", GetType().Name, TempFilePath); - openTaskCompletionSource.TrySetException(ex); - } + try + { + Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath); + using var message = response; + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); + await StreamHelper.CopyToAsync( + stream, + fileStream, + IODefaults.CopyToBufferSize, + () => Resolve(openTaskCompletionSource), + cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException ex) + { + Logger.LogInformation("Copying of {0} to {1} was canceled", GetType().Name, TempFilePath); + openTaskCompletionSource.TrySetException(ex); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error copying live stream {0} to {1}.", GetType().Name, TempFilePath); + openTaskCompletionSource.TrySetException(ex); + } - openTaskCompletionSource.TrySetResult(false); + openTaskCompletionSource.TrySetResult(false); - EnableStreamSharing = false; - await DeleteTempFiles(new List { TempFilePath }).ConfigureAwait(false); - }, CancellationToken.None); + EnableStreamSharing = false; + await DeleteTempFiles(new List { TempFilePath }).ConfigureAwait(false); + }, + CancellationToken.None); } private void Resolve(TaskCompletionSource openTaskCompletionSource) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index fc0920edfac..b8e1dc2c05b 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -15,6 +15,7 @@ using Jellyfin.Extensions.Json.Converters; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Updates; using Microsoft.Extensions.DependencyInjection; @@ -371,7 +372,7 @@ namespace Emby.Server.Implementations.Plugins var url = new Uri(packageInfo.ImageUrl); imagePath = Path.Join(path, url.Segments[^1]); - await using var fileStream = File.OpenWrite(imagePath); + await using var fileStream = AsyncFile.OpenWrite(imagePath); try { diff --git a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs index 5ff73de8194..059211a0b95 100644 --- a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs +++ b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs @@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.Serialization /// The file. public void SerializeToFile(object obj, string file) { - using (var stream = new FileStream(file, FileMode.Create)) + using (var stream = new FileStream(file, FileMode.Create, FileAccess.Write)) { SerializeToStream(obj, stream); } diff --git a/Jellyfin.Api/Controllers/ImageByNameController.cs b/Jellyfin.Api/Controllers/ImageByNameController.cs index e1b80809844..99ab7f232e9 100644 --- a/Jellyfin.Api/Controllers/ImageByNameController.cs +++ b/Jellyfin.Api/Controllers/ImageByNameController.cs @@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers } var contentType = MimeTypes.GetMimeType(path); - return File(System.IO.File.OpenRead(path), contentType); + return File(AsyncFile.OpenRead(path), contentType); } /// diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs index ec836f43e3b..8fec7d6df09 100644 --- a/Jellyfin.Api/Controllers/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/RemoteImageController.cs @@ -208,7 +208,7 @@ namespace Jellyfin.Api.Controllers var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid."); Directory.CreateDirectory(fullCacheDirectory); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true); + await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); await response.Content.CopyToAsync(fileStream).ConfigureAwait(false); var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath)); diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index bbbe5fb8dae..e6584f0fe62 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -201,7 +201,7 @@ namespace Jellyfin.Api.Controllers // For older files, assume fully static var fileShare = file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1) ? FileShare.Read : FileShare.ReadWrite; - FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, fileShare); + FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); return File(stream, "text/plain; charset=utf-8"); } diff --git a/Jellyfin.Api/Helpers/HlsHelpers.cs b/Jellyfin.Api/Helpers/HlsHelpers.cs index d1cdaf867ed..0c226f4297d 100644 --- a/Jellyfin.Api/Helpers/HlsHelpers.cs +++ b/Jellyfin.Api/Helpers/HlsHelpers.cs @@ -39,7 +39,7 @@ namespace Jellyfin.Api.Helpers FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, - FileOptions.SequentialScan); + (AsyncFile.UseAsyncIO ? FileOptions.Asynchronous : FileOptions.None) | FileOptions.SequentialScan); await using (fileStream.ConfigureAwait(false)) { using var reader = new StreamReader(fileStream); diff --git a/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs b/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs index 963e177245d..1fb4798eed9 100644 --- a/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs +++ b/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs @@ -85,8 +85,7 @@ namespace Jellyfin.Api.Helpers var fileOptions = FileOptions.SequentialScan; var allowAsyncFileRead = false; - // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (AsyncFile.UseAsyncIO) { fileOptions |= FileOptions.Asynchronous; allowAsyncFileRead = true; diff --git a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs index 499dbe84d6f..82f35fc3581 100644 --- a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs +++ b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs @@ -40,7 +40,7 @@ namespace Jellyfin.Api.Helpers _allowAsyncFileRead = false; // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (AsyncFile.UseAsyncIO) { fileOptions |= FileOptions.Asynchronous; _allowAsyncFileRead = true; diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 05fa5b1350b..b168e6d005c 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -557,7 +557,7 @@ namespace Jellyfin.Api.Helpers $"{logFilePrefix}{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{state.Request.MediaSourceId}_{Guid.NewGuid().ToString()[..8]}.log"); // FFmpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); + Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(request.Path + Environment.NewLine + Environment.NewLine + JsonSerializer.Serialize(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); await logStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false); diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs index c99c5e4efc4..6e98ad8630e 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -10,6 +10,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Users; namespace Jellyfin.Server.Implementations.Users @@ -53,7 +54,7 @@ namespace Jellyfin.Server.Implementations.Users foreach (var resetFile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*")) { SerializablePasswordReset spr; - await using (var str = File.OpenRead(resetFile)) + await using (var str = AsyncFile.OpenRead(resetFile)) { spr = await JsonSerializer.DeserializeAsync(str).ConfigureAwait(false) ?? throw new ResourceNotFoundException($"Provided path ({resetFile}) is not valid."); @@ -110,7 +111,7 @@ namespace Jellyfin.Server.Implementations.Users UserName = user.Username }; - await using (FileStream fileStream = File.OpenWrite(filePath)) + await using (FileStream fileStream = AsyncFile.OpenWrite(filePath)) { await JsonSerializer.SerializeAsync(fileStream, spr).ConfigureAwait(false); await fileStream.FlushAsync().ConfigureAwait(false); diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 7018d537fd8..6e87c8939f1 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -15,6 +15,7 @@ using Jellyfin.Server.Implementations; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Extensions; +using MediaBrowser.Model.IO; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -546,7 +547,7 @@ namespace Jellyfin.Server ?? throw new InvalidOperationException($"Invalid resource path: '{ResourcePath}'"); // Copy the resource contents to the expected file path for the config file - await using Stream dst = File.Open(configPath, FileMode.CreateNew); + await using Stream dst = new FileStream(configPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); await resource.CopyToAsync(dst).ConfigureAwait(false); } diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index a0ec3bd90dd..a524aeaa981 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -89,7 +89,7 @@ namespace MediaBrowser.MediaEncoding.Attachments CancellationToken cancellationToken) { var attachmentPath = await GetReadableFile(mediaSource.Path, mediaSource.Path, mediaSource, mediaAttachment, cancellationToken).ConfigureAwait(false); - return File.OpenRead(attachmentPath); + return AsyncFile.OpenRead(attachmentPath); } private async Task GetReadableFile( diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs index 41143c25936..d55688e3df1 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.MediaEncoding.BdInfo public bool IsDir => _impl.IsDirectory; - public System.IO.Stream OpenRead() + public Stream OpenRead() { return new FileStream( FullName, @@ -33,9 +33,9 @@ namespace MediaBrowser.MediaEncoding.BdInfo FileShare.Read); } - public System.IO.StreamReader OpenText() + public StreamReader OpenText() { - return new System.IO.StreamReader(OpenRead()); + return new StreamReader(OpenRead()); } } } diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 93093bb186b..8510a2f5440 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -1498,7 +1498,7 @@ namespace MediaBrowser.MediaEncoding.Probing { var packetBuffer = new byte[197]; - using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 1)) { fs.Read(packetBuffer); } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 608ebf44364..6f6178af24f 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -192,7 +192,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } - return File.OpenRead(fileInfo.Path); + return AsyncFile.OpenRead(fileInfo.Path); } private async Task GetReadableFile( @@ -671,7 +671,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles string text; Encoding encoding; - using (var fileStream = File.OpenRead(file)) + using (var fileStream = AsyncFile.OpenRead(file)) using (var reader = new StreamReader(fileStream, true)) { encoding = reader.CurrentEncoding; @@ -684,7 +684,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles if (!string.Equals(text, newText, StringComparison.Ordinal)) { // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)) + using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO)) using (var writer = new StreamWriter(fileStream, encoding)) { await writer.WriteAsync(newText.AsMemory(), cancellationToken).ConfigureAwait(false); @@ -750,7 +750,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles } case MediaProtocol.File: - return File.OpenRead(path); + return AsyncFile.OpenRead(path); default: throw new ArgumentOutOfRangeException(nameof(protocol)); } diff --git a/MediaBrowser.Model/IO/AsyncFile.cs b/MediaBrowser.Model/IO/AsyncFile.cs new file mode 100644 index 00000000000..b888a416395 --- /dev/null +++ b/MediaBrowser.Model/IO/AsyncFile.cs @@ -0,0 +1,34 @@ +using System; +using System.IO; + +namespace MediaBrowser.Model.IO +{ + /// + /// Helper class to create async s. + /// + public static class AsyncFile + { + /// + /// Gets a value indicating whether we should use async IO on this platform. + /// . + /// + /// Returns false on Windows; otherwise true. + public static bool UseAsyncIO => !OperatingSystem.IsWindows(); + + /// + /// Opens an existing file for reading. + /// + /// The file to be opened for reading. + /// A read-only on the specified path. + public static FileStream OpenRead(string path) + => new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, UseAsyncIO); + + /// + /// Opens an existing file for writing. + /// + /// The file to be opened for writing. + /// An unshared object on the specified path with Write access. + public static FileStream OpenWrite(string path) + => new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, UseAsyncIO); + } +} diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index fb1d4f49065..3763c6d1467 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -264,7 +264,7 @@ namespace MediaBrowser.Providers.Manager _fileSystem.SetAttributes(path, false, false); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) + await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO)) { await source.CopyToAsync(fs, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 607fd127b20..ce3ce66fa2b 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -164,7 +164,7 @@ namespace MediaBrowser.Providers.Manager { var mimeType = MimeTypes.GetMimeType(response.Path); - var stream = new FileStream(response.Path, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true); + var stream = new FileStream(response.Path, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); await _providerManager.SaveImage(item, stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 2dfaa372c5f..84d71f0b116 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -209,7 +209,7 @@ namespace MediaBrowser.Providers.Manager throw new ArgumentNullException(nameof(source)); } - var fileStream = new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, true); + var fileStream = new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken); } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs index 36d8eeb4019..81bbc26b82a 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs @@ -13,6 +13,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.AudioDb @@ -57,7 +58,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb var path = AudioDbAlbumProvider.GetAlbumInfoPath(_config.ApplicationPaths, id); - await using FileStream jsonStream = File.OpenRead(path); + await using FileStream jsonStream = AsyncFile.OpenRead(path); var obj = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); if (obj != null && obj.album != null && obj.album.Count > 0) diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs index 9f2f7fc11ed..c1226febfcd 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs @@ -66,7 +66,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb var path = GetAlbumInfoPath(_config.ApplicationPaths, id); - await using FileStream jsonStream = File.OpenRead(path); + await using FileStream jsonStream = AsyncFile.OpenRead(path); var obj = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); if (obj != null && obj.album != null && obj.album.Count > 0) @@ -173,7 +173,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true); + await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs index aa61a56f665..3ffdcdbeb2b 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs @@ -13,6 +13,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.AudioDb @@ -59,7 +60,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb var path = AudioDbArtistProvider.GetArtistInfoPath(_config.ApplicationPaths, id); - await using FileStream jsonStream = File.OpenRead(path); + await using FileStream jsonStream = AsyncFile.OpenRead(path); var obj = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); if (obj != null && obj.artists != null && obj.artists.Count > 0) diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs index 2857c6c13a4..8572b3413da 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs @@ -65,7 +65,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb var path = GetArtistInfoPath(_config.ApplicationPaths, id); - await using FileStream jsonStream = File.OpenRead(path); + await using FileStream jsonStream = AsyncFile.OpenRead(path); var obj = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); if (obj != null && obj.artists != null && obj.artists.Count > 0) @@ -155,7 +155,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb Directory.CreateDirectory(Path.GetDirectoryName(path)); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true); + await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index 1ae712e9e20..1dea3dece13 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -236,31 +236,17 @@ namespace MediaBrowser.Providers.Plugins.Omdb internal async Task GetRootObject(string imdbId, CancellationToken cancellationToken) { var path = await EnsureItemInfo(imdbId, cancellationToken).ConfigureAwait(false); - await using var stream = File.OpenRead(path); + await using var stream = AsyncFile.OpenRead(path); return await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); } internal async Task GetSeasonRootObject(string imdbId, int seasonId, CancellationToken cancellationToken) { var path = await EnsureSeasonInfo(imdbId, seasonId, cancellationToken).ConfigureAwait(false); - await using var stream = File.OpenRead(path); + await using var stream = AsyncFile.OpenRead(path); return await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); } - internal static bool IsValidSeries(Dictionary seriesProviderIds) - { - if (seriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out string id)) - { - // This check should ideally never be necessary but we're seeing some cases of this and haven't tracked them down yet. - if (!string.IsNullOrWhiteSpace(id)) - { - return true; - } - } - - return false; - } - /// Gets OMDB URL. /// Appends query string to URL. /// OMDB URL with optional query string. @@ -309,7 +295,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb imdbParam)); var rootObject = await GetDeserializedOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false); - await using FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); + await using FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); await JsonSerializer.SerializeAsync(jsonFileStream, rootObject, _jsonOptions, cancellationToken).ConfigureAwait(false); return path; @@ -349,7 +335,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb seasonId)); var rootObject = await GetDeserializedOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false); - await using FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); + await using FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); await JsonSerializer.SerializeAsync(jsonFileStream, rootObject, _jsonOptions, cancellationToken).ConfigureAwait(false); return path; diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs index 63e78d15e8b..7a057c065fa 100644 --- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs @@ -146,7 +146,7 @@ namespace MediaBrowser.Providers.Studios Directory.CreateDirectory(Path.GetDirectoryName(file)); await using var response = await httpClient.GetStreamAsync(url, cancellationToken).ConfigureAwait(false); - await using var fileStream = new FileStream(file, FileMode.Create); + await using var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); await response.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 0c791a2feea..d6c346ba12b 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -245,7 +245,7 @@ namespace MediaBrowser.Providers.Subtitles Directory.CreateDirectory(Path.GetDirectoryName(savePath)); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, FileStreamBufferSize, true); + using var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, FileStreamBufferSize, AsyncFile.UseAsyncIO); await stream.CopyToAsync(fs).ConfigureAwait(false); return; diff --git a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs index 2955104a277..97dbb3be039 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs @@ -3,6 +3,7 @@ using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Extensions.Json; using MediaBrowser.MediaEncoding.Probing; +using MediaBrowser.Model.IO; using Xunit; namespace Jellyfin.MediaEncoding.Tests @@ -14,7 +15,7 @@ namespace Jellyfin.MediaEncoding.Tests public async Task Test(string fileName) { var path = Path.Join("Test Data", fileName); - await using (var stream = File.OpenRead(path)) + await using (var stream = AsyncFile.OpenRead(path)) { var res = await JsonSerializer.DeserializeAsync(stream, JsonDefaults.Options).ConfigureAwait(false); Assert.NotNull(res); From 47be1bf69f62d72ecf81cc95347f3c8c5a6a5174 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Fri, 3 Sep 2021 10:59:40 -0600 Subject: [PATCH 033/278] Enable nullable for SchedulesDirect --- .../LiveTv/Listings/SchedulesDirect.cs | 22 ++++----- .../SchedulesDirectDtos/BroadcasterDto.cs | 10 ++-- .../SchedulesDirectDtos/CaptionDto.cs | 6 +-- .../Listings/SchedulesDirectDtos/CastDto.cs | 14 +++--- .../SchedulesDirectDtos/ChannelDto.cs | 9 ++-- .../SchedulesDirectDtos/ContentRatingDto.cs | 6 +-- .../Listings/SchedulesDirectDtos/CrewDto.cs | 12 ++--- .../Listings/SchedulesDirectDtos/DayDto.cs | 17 ++----- .../SchedulesDirectDtos/Description1000Dto.cs | 6 +-- .../SchedulesDirectDtos/Description100Dto.cs | 6 +-- .../DescriptionsProgramDto.cs | 7 ++- .../SchedulesDirectDtos/EventDetailsDto.cs | 4 +- .../SchedulesDirectDtos/GracenoteDto.cs | 2 - .../SchedulesDirectDtos/HeadendsDto.cs | 11 ++--- .../SchedulesDirectDtos/ImageDataDto.cs | 22 ++++----- .../Listings/SchedulesDirectDtos/LineupDto.cs | 12 ++--- .../SchedulesDirectDtos/LineupsDto.cs | 9 ++-- .../Listings/SchedulesDirectDtos/LogoDto.cs | 6 +-- .../Listings/SchedulesDirectDtos/MapDto.cs | 8 ++- .../SchedulesDirectDtos/MetadataDto.cs | 8 ++- .../MetadataProgramsDto.cs | 4 +- .../MetadataScheduleDto.cs | 10 ++-- .../Listings/SchedulesDirectDtos/MovieDto.cs | 7 ++- .../SchedulesDirectDtos/MultipartDto.cs | 2 - .../SchedulesDirectDtos/ProgramDetailsDto.cs | 49 +++++++++---------- .../SchedulesDirectDtos/ProgramDto.cs | 21 ++++---- .../SchedulesDirectDtos/QualityRatingDto.cs | 12 ++--- .../Listings/SchedulesDirectDtos/RatingDto.cs | 6 +-- .../SchedulesDirectDtos/RecommendationDto.cs | 6 +-- .../RequestScheduleForChannelDto.cs | 7 ++- .../SchedulesDirectDtos/ShowImagesDto.cs | 7 ++- .../SchedulesDirectDtos/StationDto.cs | 19 ++++--- .../Listings/SchedulesDirectDtos/TitleDto.cs | 4 +- .../Listings/SchedulesDirectDtos/TokenDto.cs | 15 ++++-- 34 files changed, 153 insertions(+), 213 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 8125ed57df8..41c3bafa069 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -16,7 +16,6 @@ using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos; using Jellyfin.Extensions.Json; -using MediaBrowser.Common; using MediaBrowser.Common.Net; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Cryptography; @@ -34,7 +33,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings private readonly ILogger _logger; private readonly IHttpClientFactory _httpClientFactory; private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1); - private readonly IApplicationHost _appHost; private readonly ICryptoProvider _cryptoProvider; private readonly ConcurrentDictionary _tokens = new ConcurrentDictionary(); @@ -44,12 +42,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings public SchedulesDirect( ILogger logger, IHttpClientFactory httpClientFactory, - IApplicationHost appHost, ICryptoProvider cryptoProvider) { _logger = logger; _httpClientFactory = httpClientFactory; - _appHost = appHost; _cryptoProvider = cryptoProvider; } @@ -120,8 +116,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs"); programRequestOptions.Headers.TryAddWithoutValidation("token", token); - var programsID = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct(); - programRequestOptions.Content = new StringContent("[\"" + string.Join("\", \"", programsID) + "\"]", Encoding.UTF8, MediaTypeNames.Application.Json); + var programIds = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct(); + programRequestOptions.Content = new StringContent("[\"" + string.Join("\", \"", programIds) + "\"]", Encoding.UTF8, MediaTypeNames.Application.Json); using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false); await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); @@ -225,21 +221,21 @@ namespace Emby.Server.Implementations.LiveTv.Listings string newID = programId + "T" + startAt.Ticks + "C" + channelId; - if (programInfo.AudioProperties != null) + if (programInfo.AudioProperties.Count != 0) { - if (programInfo.AudioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase))) + if (programInfo.AudioProperties.Contains("atmos", StringComparer.OrdinalIgnoreCase)) { audioType = ProgramAudio.Atmos; } - else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase))) + else if (programInfo.AudioProperties.Contains("dd 5.1", StringComparer.OrdinalIgnoreCase)) { audioType = ProgramAudio.DolbyDigital; } - else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "dd", StringComparison.OrdinalIgnoreCase))) + else if (programInfo.AudioProperties.Contains("dd", StringComparer.OrdinalIgnoreCase)) { audioType = ProgramAudio.DolbyDigital; } - else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "stereo", StringComparison.OrdinalIgnoreCase))) + else if (programInfo.AudioProperties.Contains("stereo", StringComparer.OrdinalIgnoreCase)) { audioType = ProgramAudio.Stereo; } @@ -520,7 +516,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings lineups.Add(new NameIdPair { Name = string.IsNullOrWhiteSpace(lineup.Name) ? lineup.Lineup : lineup.Name, - Id = lineup.Uri[18..] + Id = lineup.Uri?[18..] }); } } @@ -790,7 +786,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { var channelNumber = GetChannelNumber(channel); - var station = allStations.Find(item => string.Equals(item.StationId, channel.StationId, StringComparison.OrdinalIgnoreCase)) + var station = allStations.FirstOrDefault(item => string.Equals(item.StationId, channel.StationId, StringComparison.OrdinalIgnoreCase)) ?? new StationDto { StationId = channel.StationId diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs index b881b307c3d..95ac996e014 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,24 +11,24 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the city. /// [JsonPropertyName("city")] - public string City { get; set; } + public string? City { get; set; } /// /// Gets or sets the state. /// [JsonPropertyName("state")] - public string State { get; set; } + public string? State { get; set; } /// /// Gets or sets the postal code. /// [JsonPropertyName("postalCode")] - public string Postalcode { get; set; } + public string? Postalcode { get; set; } /// /// Gets or sets the country. /// [JsonPropertyName("country")] - public string Country { get; set; } + public string? Country { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs index 96b67d1eb39..f6251b9ad80 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,12 +11,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the content. ///
[JsonPropertyName("content")] - public string Content { get; set; } + public string? Content { get; set; } /// /// Gets or sets the lang. /// [JsonPropertyName("lang")] - public string Lang { get; set; } + public string? Lang { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs index dac6f5f3e01..0b7a2c63ad2 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,36 +11,36 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the billing order. ///
[JsonPropertyName("billingOrder")] - public string BillingOrder { get; set; } + public string? BillingOrder { get; set; } /// /// Gets or sets the role. /// [JsonPropertyName("role")] - public string Role { get; set; } + public string? Role { get; set; } /// /// Gets or sets the name id. /// [JsonPropertyName("nameId")] - public string NameId { get; set; } + public string? NameId { get; set; } /// /// Gets or sets the person id. /// [JsonPropertyName("personId")] - public string PersonId { get; set; } + public string? PersonId { get; set; } /// /// Gets or sets the name. /// [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } /// /// Gets or sets the character name. /// [JsonPropertyName("characterName")] - public string CharacterName { get; set; } + public string? CharacterName { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs index 8c9c2c1fccd..87c327ed82d 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs @@ -1,5 +1,4 @@ -#nullable disable - +using System; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -14,18 +13,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the list of maps. ///
[JsonPropertyName("map")] - public List Map { get; set; } + public IReadOnlyList Map { get; set; } = Array.Empty(); /// /// Gets or sets the list of stations. /// [JsonPropertyName("stations")] - public List Stations { get; set; } + public IReadOnlyList Stations { get; set; } = Array.Empty(); /// /// Gets or sets the metadata. /// [JsonPropertyName("metadata")] - public MetadataDto Metadata { get; set; } + public MetadataDto? Metadata { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs index 135b5bb08b2..c19cd2e48d9 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,12 +11,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the body. /// [JsonPropertyName("body")] - public string Body { get; set; } + public string? Body { get; set; } /// /// Gets or sets the code. /// [JsonPropertyName("code")] - public string Code { get; set; } + public string? Code { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs index 82d1001c888..f00c9accdd1 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,30 +11,30 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the billing order. /// [JsonPropertyName("billingOrder")] - public string BillingOrder { get; set; } + public string? BillingOrder { get; set; } /// /// Gets or sets the role. /// [JsonPropertyName("role")] - public string Role { get; set; } + public string? Role { get; set; } /// /// Gets or sets the name id. /// [JsonPropertyName("nameId")] - public string NameId { get; set; } + public string? NameId { get; set; } /// /// Gets or sets the person id. /// [JsonPropertyName("personId")] - public string PersonId { get; set; } + public string? PersonId { get; set; } /// /// Gets or sets the name. /// [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs index 68876b06862..1a371965cf7 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs @@ -1,5 +1,4 @@ -#nullable disable - +using System; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -10,30 +9,22 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// public class DayDto { - /// - /// Initializes a new instance of the class. - /// - public DayDto() - { - Programs = new List(); - } - /// /// Gets or sets the station id. /// [JsonPropertyName("stationID")] - public string StationId { get; set; } + public string? StationId { get; set; } /// /// Gets or sets the list of programs. /// [JsonPropertyName("programs")] - public List Programs { get; set; } + public IReadOnlyList Programs { get; set; } = Array.Empty(); /// /// Gets or sets the metadata schedule. /// [JsonPropertyName("metadata")] - public MetadataScheduleDto Metadata { get; set; } + public MetadataScheduleDto? Metadata { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs index d3e6ff39358..ca6ae7fb132 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,12 +11,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the description language. /// [JsonPropertyName("descriptionLanguage")] - public string DescriptionLanguage { get; set; } + public string? DescriptionLanguage { get; set; } /// /// Gets or sets the description. /// [JsonPropertyName("description")] - public string Description { get; set; } + public string? Description { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs index 04360266c91..1577219ed2b 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,12 +11,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the description language. /// [JsonPropertyName("descriptionLanguage")] - public string DescriptionLanguage { get; set; } + public string? DescriptionLanguage { get; set; } /// /// Gets or sets the description. /// [JsonPropertyName("description")] - public string Description { get; set; } + public string? Description { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs index 3af36ae9653..eaf4a340bdd 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs @@ -1,5 +1,4 @@ -#nullable disable - +using System; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -14,12 +13,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the list of description 100. /// [JsonPropertyName("description100")] - public List Description100 { get; set; } + public IReadOnlyList Description100 { get; set; } = Array.Empty(); /// /// Gets or sets the list of description1000. /// [JsonPropertyName("description1000")] - public List Description1000 { get; set; } + public IReadOnlyList Description1000 { get; set; } = Array.Empty(); } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs index c3b2bd9c134..fbdfb1f7168 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,6 +11,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the sub type. /// [JsonPropertyName("subType")] - public string SubType { get; set; } + public string? SubType { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs index 3d8bea362ab..6852d89d71b 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs index 1fb3decb231..b9844562f3d 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs @@ -1,5 +1,4 @@ -#nullable disable - +using System; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -14,24 +13,24 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the headend. /// [JsonPropertyName("headend")] - public string Headend { get; set; } + public string? Headend { get; set; } /// /// Gets or sets the transport. /// [JsonPropertyName("transport")] - public string Transport { get; set; } + public string? Transport { get; set; } /// /// Gets or sets the location. /// [JsonPropertyName("location")] - public string Location { get; set; } + public string? Location { get; set; } /// /// Gets or sets the list of lineups. /// [JsonPropertyName("lineups")] - public List Lineups { get; set; } + public IReadOnlyList Lineups { get; set; } = Array.Empty(); } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs index 912e680dd7e..7e554ff1ce7 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,60 +11,60 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the width. /// [JsonPropertyName("width")] - public string Width { get; set; } + public string? Width { get; set; } /// /// Gets or sets the height. /// [JsonPropertyName("height")] - public string Height { get; set; } + public string? Height { get; set; } /// /// Gets or sets the uri. /// [JsonPropertyName("uri")] - public string Uri { get; set; } + public string? Uri { get; set; } /// /// Gets or sets the size. /// [JsonPropertyName("size")] - public string Size { get; set; } + public string? Size { get; set; } /// /// Gets or sets the aspect. /// [JsonPropertyName("aspect")] - public string aspect { get; set; } + public string? aspect { get; set; } /// /// Gets or sets the category. /// [JsonPropertyName("category")] - public string Category { get; set; } + public string? Category { get; set; } /// /// Gets or sets the text. /// [JsonPropertyName("text")] - public string Text { get; set; } + public string? Text { get; set; } /// /// Gets or sets the primary. /// [JsonPropertyName("primary")] - public string Primary { get; set; } + public string? Primary { get; set; } /// /// Gets or sets the tier. /// [JsonPropertyName("tier")] - public string Tier { get; set; } + public string? Tier { get; set; } /// /// Gets or sets the caption. /// [JsonPropertyName("caption")] - public CaptionDto Caption { get; set; } + public CaptionDto? Caption { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs index 52e920aa617..676e7452586 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,30 +11,30 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the linup. /// [JsonPropertyName("lineup")] - public string Lineup { get; set; } + public string? Lineup { get; set; } /// /// Gets or sets the lineup name. /// [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } /// /// Gets or sets the transport. /// [JsonPropertyName("transport")] - public string Transport { get; set; } + public string? Transport { get; set; } /// /// Gets or sets the location. /// [JsonPropertyName("location")] - public string Location { get; set; } + public string? Location { get; set; } /// /// Gets or sets the uri. /// [JsonPropertyName("uri")] - public string Uri { get; set; } + public string? Uri { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs index 15139ba3b20..b0ee0ac8eb2 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs @@ -1,5 +1,4 @@ -#nullable disable - +using System; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -20,18 +19,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the server id. /// [JsonPropertyName("serverID")] - public string ServerId { get; set; } + public string? ServerId { get; set; } /// /// Gets or sets the datetime. /// [JsonPropertyName("datetime")] - public string Datetime { get; set; } + public string? Datetime { get; set; } /// /// Gets or sets the list of lineups. /// [JsonPropertyName("lineups")] - public List Lineups { get; set; } + public IReadOnlyList Lineups { get; set; } = Array.Empty(); } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs index 7b235ed7f69..fecc55e0378 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,7 +11,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the url. /// [JsonPropertyName("URL")] - public string Url { get; set; } + public string? Url { get; set; } /// /// Gets or sets the height. @@ -31,6 +29,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the md5. /// [JsonPropertyName("md5")] - public string Md5 { get; set; } + public string? Md5 { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs index 5140277b2cb..cc1841c1761 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,19 +11,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the station id. /// [JsonPropertyName("stationID")] - public string StationId { get; set; } + public string? StationId { get; set; } /// /// Gets or sets the channel. /// [JsonPropertyName("channel")] - public string Channel { get; set; } + public string? Channel { get; set; } /// /// Gets or sets the logical channel number. /// [JsonPropertyName("logicalChannelNumber")] - public string LogicalChannelNumber { get; set; } + public string? LogicalChannelNumber { get; set; } /// /// Gets or sets the uhfvhf. diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs index 5a3893a35f9..40faa493c5d 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,18 +11,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the linup. /// [JsonPropertyName("lineup")] - public string Lineup { get; set; } + public string? Lineup { get; set; } /// /// Gets or sets the modified timestamp. /// [JsonPropertyName("modified")] - public string Modified { get; set; } + public string? Modified { get; set; } /// /// Gets or sets the transport. /// [JsonPropertyName("transport")] - public string Transport { get; set; } + public string? Transport { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs index 4057e980246..843ef9091d6 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,6 +11,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the gracenote object. /// [JsonPropertyName("gracenote")] - public GracenoteDto Gracenote { get; set; } + public GracenoteDto? Gracenote { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs index 4979296da48..68fbeec46d3 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,25 +11,25 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the modified timestamp. /// [JsonPropertyName("modified")] - public string Modified { get; set; } + public string? Modified { get; set; } /// /// Gets or sets the md5. /// [JsonPropertyName("md5")] - public string Md5 { get; set; } + public string? Md5 { get; set; } /// /// Gets or sets the start date. /// [JsonPropertyName("startDate")] - public string StartDate { get; set; } + public string? StartDate { get; set; } /// /// Gets or sets the end date. /// [JsonPropertyName("endDate")] - public string EndDate { get; set; } + public string? EndDate { get; set; } /// /// Gets or sets the days count. diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs index 48d731d8906..31bef423b22 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs @@ -1,5 +1,4 @@ -#nullable disable - +using System; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -14,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the year. /// [JsonPropertyName("year")] - public string Year { get; set; } + public string? Year { get; set; } /// /// Gets or sets the duration. @@ -26,6 +25,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the list of quality rating. /// [JsonPropertyName("qualityRating")] - public List QualityRating { get; set; } + public IReadOnlyList QualityRating { get; set; } = Array.Empty(); } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs index 42eddfff261..e8b15dc07c8 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs index a84c47c1292..bfaed1e081b 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs @@ -1,5 +1,4 @@ -#nullable disable - +using System; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -14,85 +13,85 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the audience. /// [JsonPropertyName("audience")] - public string Audience { get; set; } + public string? Audience { get; set; } /// /// Gets or sets the program id. /// [JsonPropertyName("programID")] - public string ProgramId { get; set; } + public string? ProgramId { get; set; } /// /// Gets or sets the list of titles. /// [JsonPropertyName("titles")] - public List Titles { get; set; } + public IReadOnlyList Titles { get; set; } = Array.Empty(); /// /// Gets or sets the event details object. /// [JsonPropertyName("eventDetails")] - public EventDetailsDto EventDetails { get; set; } + public EventDetailsDto? EventDetails { get; set; } /// /// Gets or sets the descriptions. /// [JsonPropertyName("descriptions")] - public DescriptionsProgramDto Descriptions { get; set; } + public DescriptionsProgramDto? Descriptions { get; set; } /// /// Gets or sets the original air date. /// [JsonPropertyName("originalAirDate")] - public string OriginalAirDate { get; set; } + public string? OriginalAirDate { get; set; } /// /// Gets or sets the list of genres. /// [JsonPropertyName("genres")] - public List Genres { get; set; } + public IReadOnlyList Genres { get; set; } = Array.Empty(); /// /// Gets or sets the episode title. /// [JsonPropertyName("episodeTitle150")] - public string EpisodeTitle150 { get; set; } + public string? EpisodeTitle150 { get; set; } /// /// Gets or sets the list of metadata. /// [JsonPropertyName("metadata")] - public List Metadata { get; set; } + public IReadOnlyList Metadata { get; set; } = Array.Empty(); /// /// Gets or sets the list of content raitings. /// [JsonPropertyName("contentRating")] - public List ContentRating { get; set; } + public IReadOnlyList ContentRating { get; set; } = Array.Empty(); /// /// Gets or sets the list of cast. /// [JsonPropertyName("cast")] - public List Cast { get; set; } + public IReadOnlyList Cast { get; set; } = Array.Empty(); /// /// Gets or sets the list of crew. /// [JsonPropertyName("crew")] - public List Crew { get; set; } + public IReadOnlyList Crew { get; set; } = Array.Empty(); /// /// Gets or sets the entity type. /// [JsonPropertyName("entityType")] - public string EntityType { get; set; } + public string? EntityType { get; set; } /// /// Gets or sets the show type. /// [JsonPropertyName("showType")] - public string ShowType { get; set; } + public string? ShowType { get; set; } /// /// Gets or sets a value indicating whether there is image artwork. @@ -104,54 +103,54 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the primary image. /// [JsonPropertyName("primaryImage")] - public string PrimaryImage { get; set; } + public string? PrimaryImage { get; set; } /// /// Gets or sets the thumb image. /// [JsonPropertyName("thumbImage")] - public string ThumbImage { get; set; } + public string? ThumbImage { get; set; } /// /// Gets or sets the backdrop image. /// [JsonPropertyName("backdropImage")] - public string BackdropImage { get; set; } + public string? BackdropImage { get; set; } /// /// Gets or sets the banner image. /// [JsonPropertyName("bannerImage")] - public string BannerImage { get; set; } + public string? BannerImage { get; set; } /// /// Gets or sets the image id. /// [JsonPropertyName("imageID")] - public string ImageId { get; set; } + public string? ImageId { get; set; } /// /// Gets or sets the md5. /// [JsonPropertyName("md5")] - public string Md5 { get; set; } + public string? Md5 { get; set; } /// /// Gets or sets the list of content advisory. /// [JsonPropertyName("contentAdvisory")] - public List ContentAdvisory { get; set; } + public IReadOnlyList ContentAdvisory { get; set; } = Array.Empty(); /// /// Gets or sets the movie object. /// [JsonPropertyName("movie")] - public MovieDto Movie { get; set; } + public MovieDto? Movie { get; set; } /// /// Gets or sets the list of recommendations. /// [JsonPropertyName("recommendations")] - public List Recommendations { get; set; } + public IReadOnlyList Recommendations { get; set; } = Array.Empty(); } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs index ad5389100c7..ff039c1cf16 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs @@ -1,5 +1,4 @@ -#nullable disable - +using System; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -14,13 +13,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the program id. /// [JsonPropertyName("programID")] - public string ProgramId { get; set; } + public string? ProgramId { get; set; } /// /// Gets or sets the air date time. /// [JsonPropertyName("airDateTime")] - public string AirDateTime { get; set; } + public string? AirDateTime { get; set; } /// /// Gets or sets the duration. @@ -32,25 +31,25 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the md5. /// [JsonPropertyName("md5")] - public string Md5 { get; set; } + public string? Md5 { get; set; } /// /// Gets or sets the list of audio properties. /// [JsonPropertyName("audioProperties")] - public List AudioProperties { get; set; } + public IReadOnlyList AudioProperties { get; set; } = Array.Empty(); /// /// Gets or sets the list of video properties. /// [JsonPropertyName("videoProperties")] - public List VideoProperties { get; set; } + public IReadOnlyList VideoProperties { get; set; } = Array.Empty(); /// /// Gets or sets the list of ratings. /// [JsonPropertyName("ratings")] - public List Ratings { get; set; } + public IReadOnlyList Ratings { get; set; } = Array.Empty(); /// /// Gets or sets a value indicating whether this program is new. @@ -62,13 +61,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the multipart object. /// [JsonPropertyName("multipart")] - public MultipartDto Multipart { get; set; } + public MultipartDto? Multipart { get; set; } /// /// Gets or sets the live tape delay. /// [JsonPropertyName("liveTapeDelay")] - public string LiveTapeDelay { get; set; } + public string? LiveTapeDelay { get; set; } /// /// Gets or sets a value indicating whether this is the premiere. @@ -86,6 +85,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the premiere or finale. /// [JsonPropertyName("isPremiereOrFinale")] - public string IsPremiereOrFinale { get; set; } + public string? IsPremiereOrFinale { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs index 5cd0a745954..c5ddcf7c51a 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,30 +11,30 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the ratings body. /// [JsonPropertyName("ratingsBody")] - public string RatingsBody { get; set; } + public string? RatingsBody { get; set; } /// /// Gets or sets the rating. /// [JsonPropertyName("rating")] - public string Rating { get; set; } + public string? Rating { get; set; } /// /// Gets or sets the min rating. /// [JsonPropertyName("minRating")] - public string MinRating { get; set; } + public string? MinRating { get; set; } /// /// Gets or sets the max rating. /// [JsonPropertyName("maxRating")] - public string MaxRating { get; set; } + public string? MaxRating { get; set; } /// /// Gets or sets the increment. /// [JsonPropertyName("increment")] - public string Increment { get; set; } + public string? Increment { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs index 948b831446d..e04b619a4d2 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,12 +11,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the body. /// [JsonPropertyName("body")] - public string Body { get; set; } + public string? Body { get; set; } /// /// Gets or sets the code. /// [JsonPropertyName("code")] - public string Code { get; set; } + public string? Code { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs index 1308f45ce74..c8f79fd1c28 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,12 +11,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the program id. /// [JsonPropertyName("programID")] - public string ProgramId { get; set; } + public string? ProgramId { get; set; } /// /// Gets or sets the title. /// [JsonPropertyName("title120")] - public string Title120 { get; set; } + public string? Title120 { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs index fb7a31ac85e..0cd05709b35 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs @@ -1,5 +1,4 @@ -#nullable disable - +using System; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -14,12 +13,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the station id. /// [JsonPropertyName("stationID")] - public string StationId { get; set; } + public string? StationId { get; set; } /// /// Gets or sets the list of dates. /// [JsonPropertyName("date")] - public List Date { get; set; } + public IReadOnlyList Date { get; set; } = Array.Empty(); } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs index 34302370d6c..84e224b71ec 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs @@ -1,5 +1,4 @@ -#nullable disable - +using System; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -14,12 +13,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the program id. /// [JsonPropertyName("programID")] - public string ProgramId { get; set; } + public string? ProgramId { get; set; } /// /// Gets or sets the list of data. /// [JsonPropertyName("data")] - public List Data { get; set; } + public IReadOnlyList Data { get; set; } = Array.Empty(); } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs index 12f3576c65b..1b76b22e32a 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs @@ -1,5 +1,4 @@ -#nullable disable - +using System; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -14,49 +13,49 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the station id. /// [JsonPropertyName("stationID")] - public string StationId { get; set; } + public string? StationId { get; set; } /// /// Gets or sets the name. /// [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } /// /// Gets or sets the callsign. /// [JsonPropertyName("callsign")] - public string Callsign { get; set; } + public string? Callsign { get; set; } /// /// Gets or sets the broadcast language. /// [JsonPropertyName("broadcastLanguage")] - public List BroadcastLanguage { get; set; } + public IReadOnlyList BroadcastLanguage { get; set; } = Array.Empty(); /// /// Gets or sets the description language. /// [JsonPropertyName("descriptionLanguage")] - public List DescriptionLanguage { get; set; } + public IReadOnlyList DescriptionLanguage { get; set; } = Array.Empty(); /// /// Gets or sets the broadcaster. /// [JsonPropertyName("broadcaster")] - public BroadcasterDto Broadcaster { get; set; } + public BroadcasterDto? Broadcaster { get; set; } /// /// Gets or sets the affiliate. /// [JsonPropertyName("affiliate")] - public string Affiliate { get; set; } + public string? Affiliate { get; set; } /// /// Gets or sets the logo. /// [JsonPropertyName("logo")] - public LogoDto Logo { get; set; } + public LogoDto? Logo { get; set; } /// /// Gets or set a value indicating whether it is commercial free. diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs index 06c95524b01..61cd4a9b004 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -13,6 +11,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the title. /// [JsonPropertyName("title120")] - public string Title120 { get; set; } + public string? Title120 { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs index c3ec1c7d61f..fb5ed95ac9a 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs @@ -1,5 +1,4 @@ -#nullable disable - +using System; using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos @@ -19,18 +18,24 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the response message. /// [JsonPropertyName("message")] - public string Message { get; set; } + public string? Message { get; set; } /// /// Gets or sets the server id. /// [JsonPropertyName("serverID")] - public string ServerId { get; set; } + public string? ServerId { get; set; } /// /// Gets or sets the token. /// [JsonPropertyName("token")] - public string Token { get; set; } + public string? Token { get; set; } + + /// + /// Gets or sets the current datetime. + /// + [JsonPropertyName("datetime")] + public DateTime? DateTime { get; set; } } } From 058baf5abd282901a8ffbce40041c61daf89eec8 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Fri, 3 Sep 2021 12:35:32 -0600 Subject: [PATCH 034/278] Add IReadOnlyList extensions --- .../ReadOnlyListExtension.cs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/Jellyfin.Extensions/ReadOnlyListExtension.cs diff --git a/src/Jellyfin.Extensions/ReadOnlyListExtension.cs b/src/Jellyfin.Extensions/ReadOnlyListExtension.cs new file mode 100644 index 00000000000..7785cfb495e --- /dev/null +++ b/src/Jellyfin.Extensions/ReadOnlyListExtension.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; + +namespace Jellyfin.Extensions +{ + /// + /// Static extensions for the interface. + /// + public static class ReadOnlyListExtension + { + /// + /// Finds the index of the desired item. + /// + /// The source list. + /// The value to fine. + /// The type of item to find. + /// Index if found, else -1. + public static int IndexOf(this IReadOnlyList source, T value) + { + if (source is IList list) + { + return list.IndexOf(value); + } + + for (int i = 0; i < source.Count; i++) + { + if (Equals(value, source[i])) + { + return i; + } + } + + return -1; + } + + /// + /// Finds the index of the predicate. + /// + /// The source list. + /// The value to find. + /// The type of item to find. + /// Index if found, else -1. + public static int FindIndex(this IReadOnlyList source, Predicate match) + { + if (source is List list) + { + return list.FindIndex(match); + } + + for (int i = 0; i < source.Count; i++) + { + if (match(source[i])) + { + return i; + } + } + + return -1; + } + } +} From 47e24a2cf77e6c3fe50eb5398fa56950a047ed7b Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Fri, 3 Sep 2021 12:35:52 -0600 Subject: [PATCH 035/278] Add SchedulesDirect json tests --- .../LiveTv/Listings/SchedulesDirect.cs | 62 ++--- .../SchedulesDirectDtos/ImageDataDto.cs | 2 +- .../Listings/SchedulesDirectDtos/LineupDto.cs | 6 + .../SchedulesDirectDtos/LineupsDto.cs | 2 +- .../Listings/SchedulesDirectDtos/MapDto.cs | 12 + .../MetadataProgramsDto.cs | 2 +- .../SchedulesDirectDtos/ProgramDetailsDto.cs | 2 +- .../SchedulesDirectDtos/ProgramDto.cs | 2 +- .../Listings/SchedulesDirectDtos/TokenDto.cs | 6 + ...llyfin.Server.Implementations.Tests.csproj | 3 + .../SchedulesDirectDeserializeTests.cs | 245 ++++++++++++++++++ .../TestData/headends_response.json | 113 ++++++++ .../TestData/lineup_response.json | 18 ++ .../TestData/lineups_response.json | 40 +++ .../TestData/metadata_programs_response.json | 51 ++++ .../TestData/programs_response.json | 245 ++++++++++++++++++ .../TestData/schedules_request.json | 16 ++ .../TestData/schedules_response.json | 35 +++ .../TestData/token_live_response.json | 7 + .../TestData/token_offline_response.json | 8 + 20 files changed, 841 insertions(+), 36 deletions(-) create mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/headends_response.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/lineup_response.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/lineups_response.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/metadata_programs_response.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/programs_response.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/schedules_request.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/schedules_response.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/token_live_response.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/token_offline_response.json diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 41c3bafa069..e8562b8c577 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -9,12 +9,14 @@ using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Json; using System.Net.Mime; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos; +using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using MediaBrowser.Common.Net; using MediaBrowser.Controller.LiveTv; @@ -110,19 +112,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings options.Headers.TryAddWithoutValidation("token", token); using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false); await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - var dailySchedules = await JsonSerializer.DeserializeAsync>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false); - _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId); + var dailySchedules = await JsonSerializer.DeserializeAsync>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules!.Count, channelId); using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs"); programRequestOptions.Headers.TryAddWithoutValidation("token", token); var programIds = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct(); - programRequestOptions.Content = new StringContent("[\"" + string.Join("\", \"", programIds) + "\"]", Encoding.UTF8, MediaTypeNames.Application.Json); + programRequestOptions.Content = new StringContent(JsonSerializer.Serialize(programIds), Encoding.UTF8, MediaTypeNames.Application.Json); using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false); await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - var programDetails = await JsonSerializer.DeserializeAsync>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false); - var programDict = programDetails.ToDictionary(p => p.ProgramId, y => y); + var programDetails = await JsonSerializer.DeserializeAsync>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + var programDict = programDetails!.ToDictionary(p => p.ProgramId, y => y); var programIdsWithImages = programDetails .Where(p => p.HasImageArtwork).Select(p => p.ProgramId) @@ -138,6 +140,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings // schedule.ProgramId + " which says it has images? " + // programDict[schedule.ProgramId].hasImageArtwork); + if (string.IsNullOrEmpty(schedule.ProgramId)) + { + continue; + } + if (images != null) { var imageIndex = images.FindIndex(i => i.ProgramId == schedule.ProgramId[..10]); @@ -145,7 +152,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { var programEntry = programDict[schedule.ProgramId]; - var allImages = images[imageIndex].Data ?? new List(); + var allImages = images[imageIndex].Data; var imagesWithText = allImages.Where(i => string.Equals(i.Text, "yes", StringComparison.OrdinalIgnoreCase)); var imagesWithoutText = allImages.Where(i => string.Equals(i.Text, "no", StringComparison.OrdinalIgnoreCase)); @@ -213,7 +220,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings private ProgramInfo GetProgram(string channelId, ProgramDto programInfo, ProgramDetailsDto details) { - var startAt = GetDate(programInfo.AirDateTime); + if (programInfo.AirDateTime == null) + { + return null; + } + + var startAt = programInfo.AirDateTime.Value; var endAt = startAt.AddSeconds(programInfo.Duration); var audioType = ProgramAudio.Stereo; @@ -351,9 +363,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings } } - if (!string.IsNullOrWhiteSpace(details.OriginalAirDate)) + if (details.OriginalAirDate != null) { - info.OriginalAirDate = DateTime.Parse(details.OriginalAirDate, CultureInfo.InvariantCulture); + info.OriginalAirDate = details.OriginalAirDate; info.ProductionYear = info.OriginalAirDate.Value.Year; } @@ -380,18 +392,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings return info; } - private static DateTime GetDate(string value) - { - var date = DateTime.ParseExact(value, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'", CultureInfo.InvariantCulture); - - if (date.Kind != DateTimeKind.Utc) - { - date = DateTime.SpecifyKind(date, DateTimeKind.Utc); - } - - return date; - } - private string GetProgramImage(string apiUrl, IEnumerable images, bool returnDefaultImage, double desiredAspect) { var match = images @@ -445,14 +445,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings return result; } - private async Task> GetImageForPrograms( + private async Task> GetImageForPrograms( ListingsProviderInfo info, IReadOnlyList programIds, CancellationToken cancellationToken) { if (programIds.Count == 0) { - return new List(); + return Array.Empty(); } StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13)); @@ -476,13 +476,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings { using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false); await using var response = await innerResponse2.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - return await JsonSerializer.DeserializeAsync>(response, _jsonOptions, cancellationToken).ConfigureAwait(false); + return await JsonSerializer.DeserializeAsync>(response, _jsonOptions, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "Error getting image info from schedules direct"); - return new List(); + return Array.Empty(); } } @@ -505,7 +505,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false); await using var response = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - var root = await JsonSerializer.DeserializeAsync>(response, _jsonOptions, cancellationToken).ConfigureAwait(false); + var root = await JsonSerializer.DeserializeAsync>(response, _jsonOptions, cancellationToken).ConfigureAwait(false); if (root != null) { @@ -647,7 +647,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings response.EnsureSuccessStatusCode(); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var root = await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); - if (string.Equals(root.Message, "OK", StringComparison.Ordinal)) + if (string.Equals(root?.Message, "OK", StringComparison.Ordinal)) { _logger.LogInformation("Authenticated with Schedules Direct token: {Token}", root.Token); return root.Token; @@ -704,12 +704,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings using var response = httpResponse.Content; var root = await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); - return root.Lineups.Any(i => string.Equals(info.ListingsId, i.Lineup, StringComparison.OrdinalIgnoreCase)); + return root?.Lineups.Any(i => string.Equals(info.ListingsId, i.Lineup, StringComparison.OrdinalIgnoreCase)) ?? false; } catch (HttpRequestException ex) { // SchedulesDirect returns 400 if no lineups are configured. - if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.BadRequest) + if (ex.StatusCode is HttpStatusCode.BadRequest) { return false; } @@ -775,10 +775,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false); await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var root = await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); - _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.Map.Count); + _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root!.Map.Count); _logger.LogInformation("Mapping Stations to Channel"); - var allStations = root.Stations ?? new List(); + var allStations = root.Stations; var map = root.Map; var list = new List(map.Count); diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs index 7e554ff1ce7..a1ae3ca6d4e 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs @@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the aspect. /// [JsonPropertyName("aspect")] - public string? aspect { get; set; } + public string? Aspect { get; set; } /// /// Gets or sets the category. diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs index 676e7452586..3dc64e5d8a5 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs @@ -36,5 +36,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// [JsonPropertyName("uri")] public string? Uri { get; set; } + + /// + /// Gets or sets a value indicating whether this lineup was deleted. + /// + [JsonPropertyName("isDeleted")] + public bool? IsDeleted { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs index b0ee0ac8eb2..a635c598702 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the datetime. /// [JsonPropertyName("datetime")] - public string? Datetime { get; set; } + public DateTime? Datetime { get; set; } /// /// Gets or sets the list of lineups. diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs index cc1841c1761..2420307b4f9 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs @@ -19,6 +19,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos [JsonPropertyName("channel")] public string? Channel { get; set; } + /// + /// Gets or sets the provider callsign. + /// + [JsonPropertyName("providerCallsign")] + public string? ProvderCallsign { get; set; } + /// /// Gets or sets the logical channel number. /// @@ -42,5 +48,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// [JsonPropertyName("atscMinor")] public int AtscMinor { get; set; } + + /// + /// Gets or sets the match type. + /// + [JsonPropertyName("matchType")] + public string? MatchType { get;set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs index 843ef9091d6..43f29015667 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs @@ -10,7 +10,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// /// Gets or sets the gracenote object. /// - [JsonPropertyName("gracenote")] + [JsonPropertyName("Gracenote")] public GracenoteDto? Gracenote { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs index bfaed1e081b..84c48f67f34 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs @@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the original air date. /// [JsonPropertyName("originalAirDate")] - public string? OriginalAirDate { get; set; } + public DateTime? OriginalAirDate { get; set; } /// /// Gets or sets the list of genres. diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs index ff039c1cf16..60389b45bf3 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs @@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the air date time. /// [JsonPropertyName("airDateTime")] - public string? AirDateTime { get; set; } + public DateTime? AirDateTime { get; set; } /// /// Gets or sets the duration. diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs index fb5ed95ac9a..561f79c5af5 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs @@ -37,5 +37,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// [JsonPropertyName("datetime")] public DateTime? DateTime { get; set; } + + /// + /// Gets or sets the response message. + /// + [JsonPropertyName("response")] + public string? Response { get; set; } } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 9b6ab7bdf5b..e02e357ca07 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -16,6 +16,9 @@ PreserveNewest + + PreserveNewest + diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs new file mode 100644 index 00000000000..84ac3ea4774 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos; +using Jellyfin.Extensions.Json; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect +{ + public class SchedulesDirectDeserializeTests + { + private readonly JsonSerializerOptions _jsonOptions; + + public SchedulesDirectDeserializeTests() + { + _jsonOptions = JsonDefaults.Options; + } + + /// + /// /token reponse. + /// + [Fact] + public void Deserialize_Token_Response_Live_Success() + { + var bytes = File.ReadAllBytes("LiveTv/SchedulesDirect/TestData/token_live_response.json"); + var tokenDto = JsonSerializer.Deserialize(bytes); + + Assert.NotNull(tokenDto); + Assert.Equal(0, tokenDto!.Code); + Assert.Equal("OK", tokenDto.Message); + Assert.Equal("AWS-SD-web.1", tokenDto.ServerId); + Assert.Equal(new DateTime(2016, 08, 23, 13, 55, 25, DateTimeKind.Utc), tokenDto.DateTime); + Assert.Equal("f3fca79989cafe7dead71beefedc812b", tokenDto.Token); + } + + /// + /// /token response. + /// + [Fact] + public void Deserialize_Token_Response_Offline_Success() + { + var bytes = File.ReadAllBytes("LiveTv/SchedulesDirect/TestData/token_offline_response.json"); + var tokenDto = JsonSerializer.Deserialize(bytes); + + Assert.NotNull(tokenDto); + Assert.Equal(3_000, tokenDto!.Code); + Assert.Equal("Server offline for maintenance.", tokenDto.Message); + Assert.Equal("20141201.web.1", tokenDto.ServerId); + Assert.Equal(new DateTime(2015, 04, 23, 00, 03, 32, DateTimeKind.Utc), tokenDto.DateTime); + Assert.Equal("CAFEDEADBEEFCAFEDEADBEEFCAFEDEADBEEFCAFE", tokenDto.Token); + Assert.Equal("SERVICE_OFFLINE", tokenDto.Response); + } + + /// + /// /schedules request. + /// + [Fact] + public void Serialize_Schedule_Request_Success() + { + var expectedString = File.ReadAllText("LiveTv/SchedulesDirect/TestData/schedules_request.json").Trim(); + + var requestObject = new RequestScheduleForChannelDto[] + { + new RequestScheduleForChannelDto + { + StationId = "20454", + Date = new[] + { + "2015-03-13", + "2015-03-17" + } + }, + new RequestScheduleForChannelDto + { + StationId = "10021", + Date = new[] + { + "2015-03-12", + "2015-03-13" + } + } + }; + + var jsonOptions = new JsonSerializerOptions(_jsonOptions) + { + WriteIndented = true + }; + + var requestString = JsonSerializer.Serialize(requestObject, jsonOptions); + Assert.Equal(expectedString, requestString); + } + + /// + /// /schedules response. + /// + [Fact] + public void Deserialize_Schedule_Response_Success() + { + var bytes = File.ReadAllBytes("LiveTv/SchedulesDirect/TestData/schedules_response.json"); + var days = JsonSerializer.Deserialize>(bytes); + + Assert.NotNull(days); + Assert.Equal(1, days!.Count); + + var dayDto = days[0]; + Assert.Equal("20454", dayDto.StationId); + Assert.Equal(2, dayDto.Programs.Count); + + Assert.Equal("SH005371070000", dayDto.Programs[0].ProgramId); + Assert.Equal(new DateTime(2015, 03, 03, 00, 00, 00, DateTimeKind.Utc), dayDto.Programs[0].AirDateTime); + Assert.Equal(1_800, dayDto.Programs[0].Duration); + Assert.Equal("Sy8HEMBPcuiAx3FBukUhKQ", dayDto.Programs[0].Md5); + Assert.True(dayDto.Programs[0].New); + Assert.Equal(2, dayDto.Programs[0].AudioProperties.Count); + Assert.Equal("stereo", dayDto.Programs[0].AudioProperties[0]); + Assert.Equal("cc", dayDto.Programs[0].AudioProperties[1]); + Assert.Equal(1, dayDto.Programs[0].VideoProperties.Count); + Assert.Equal("hdtv", dayDto.Programs[0].VideoProperties[0]); + } + + /// + /// /programs response. + /// + [Fact] + public void Deserialize_Program_Response_Success() + { + var bytes = File.ReadAllBytes("LiveTv/SchedulesDirect/TestData/programs_response.json"); + var programDtos = JsonSerializer.Deserialize>(bytes); + + Assert.NotNull(programDtos); + Assert.Equal(2, programDtos!.Count); + Assert.Equal("EP000000060003", programDtos[0].ProgramId); + Assert.Equal(1, programDtos[0].Titles.Count); + Assert.Equal("'Allo 'Allo!", programDtos[0].Titles[0].Title120); + Assert.Equal("Series", programDtos[0].EventDetails?.SubType); + Assert.Equal("en", programDtos[0].Descriptions?.Description1000[0].DescriptionLanguage); + Assert.Equal("A disguised British Intelligence officer is sent to help the airmen.", programDtos[0].Descriptions?.Description1000[0].Description); + Assert.Equal(new DateTime(1985, 11, 04), programDtos[0].OriginalAirDate); + Assert.Equal(1, programDtos[0].Genres.Count); + Assert.Equal("Sitcom", programDtos[0].Genres[0]); + Assert.Equal("The Poloceman Cometh", programDtos[0].EpisodeTitle150); + Assert.Equal(2, programDtos[0].Metadata[0].Gracenote?.Season); + Assert.Equal(3, programDtos[0].Metadata[0].Gracenote?.Episode); + Assert.Equal(13, programDtos[0].Cast.Count); + Assert.Equal("383774", programDtos[0].Cast[0].PersonId); + Assert.Equal("392649", programDtos[0].Cast[0].NameId); + Assert.Equal("Gorden Kaye", programDtos[0].Cast[0].Name); + Assert.Equal("Actor", programDtos[0].Cast[0].Role); + Assert.Equal("01", programDtos[0].Cast[0].BillingOrder); + Assert.Equal(3, programDtos[0].Crew.Count); + Assert.Equal("354407", programDtos[0].Crew[0].PersonId); + Assert.Equal("363281", programDtos[0].Crew[0].NameId); + Assert.Equal("David Croft", programDtos[0].Crew[0].Name); + Assert.Equal("Director", programDtos[0].Crew[0].Role); + Assert.Equal("01", programDtos[0].Crew[0].BillingOrder); + } + + /// + /// /metadata/programs response. + /// + [Fact] + public void Deserialize_Metadata_Programs_Response_Success() + { + var bytes = File.ReadAllBytes("LiveTv/SchedulesDirect/TestData/metadata_programs_response.json"); + var showImagesDtos = JsonSerializer.Deserialize>(bytes); + + Assert.NotNull(showImagesDtos); + Assert.Equal(1, showImagesDtos!.Count); + Assert.Equal("SH00712240", showImagesDtos[0].ProgramId); + Assert.Equal(4, showImagesDtos[0].Data.Count); + Assert.Equal("135", showImagesDtos[0].Data[0].Width); + Assert.Equal("180", showImagesDtos[0].Data[0].Height); + Assert.Equal("assets/p282288_b_v2_aa.jpg", showImagesDtos[0].Data[0].Uri); + Assert.Equal("Sm", showImagesDtos[0].Data[0].Size); + Assert.Equal("3x4", showImagesDtos[0].Data[0].Aspect); + Assert.Equal("Banner-L3", showImagesDtos[0].Data[0].Category); + Assert.Equal("yes", showImagesDtos[0].Data[0].Text); + Assert.Equal("true", showImagesDtos[0].Data[0].Primary); + Assert.Equal("Series", showImagesDtos[0].Data[0].Tier); + } + + /// + /// /headends response. + /// + [Fact] + public void Deserialize_Headends_Response_Success() + { + var bytes = File.ReadAllBytes("LiveTv/SchedulesDirect/TestData/headends_response.json"); + var headendsDtos = JsonSerializer.Deserialize>(bytes); + + Assert.NotNull(headendsDtos); + Assert.Equal(8, headendsDtos!.Count); + Assert.Equal("CA00053", headendsDtos[0].Headend); + Assert.Equal("Cable", headendsDtos[0].Transport); + Assert.Equal("Beverly Hills", headendsDtos[0].Location); + Assert.Equal(2, headendsDtos[0].Lineups.Count); + Assert.Equal("Time Warner Cable - Cable", headendsDtos[0].Lineups[0].Name); + Assert.Equal("USA-CA00053-DEFAULT", headendsDtos[0].Lineups[0].Lineup); + Assert.Equal("/20141201/lineups/USA-CA00053-DEFAULT", headendsDtos[0].Lineups[0].Uri); + } + + /// + /// /lineups response. + /// + [Fact] + public void Deserialize_Lineups_Response_Success() + { + var bytes = File.ReadAllBytes("LiveTv/SchedulesDirect/TestData/lineups_response.json"); + var lineupsDto = JsonSerializer.Deserialize(bytes); + + Assert.NotNull(lineupsDto); + Assert.Equal(0, lineupsDto!.Code); + Assert.Equal("20141201.web.1", lineupsDto.ServerId); + Assert.Equal(new DateTime(2015, 04, 17, 14, 22, 17, DateTimeKind.Utc), lineupsDto.Datetime); + Assert.Equal(5, lineupsDto.Lineups.Count); + Assert.Equal("GBR-0001317-DEFAULT", lineupsDto.Lineups[0].Lineup); + Assert.Equal("Freeview - Carlton - LWT (Southeast)", lineupsDto.Lineups[0].Name); + Assert.Equal("DVB-T", lineupsDto.Lineups[0].Transport); + Assert.Equal("London", lineupsDto.Lineups[0].Location); + Assert.Equal("/20141201/lineups/GBR-0001317-DEFAULT", lineupsDto.Lineups[0].Uri); + + Assert.Equal("DELETED LINEUP", lineupsDto.Lineups[4].Name); + Assert.True(lineupsDto.Lineups[4].IsDeleted); + } + + /// + /// /lineup/:id response. + /// + [Fact] + public void Deserialize_Lineup_Response_Success() + { + var bytes = File.ReadAllBytes("LiveTv/SchedulesDirect/TestData/lineup_response.json"); + var channelDto = JsonSerializer.Deserialize(bytes); + + Assert.NotNull(channelDto); + Assert.Equal(2, channelDto!.Map.Count); + Assert.Equal("24326", channelDto.Map[0].StationId); + Assert.Equal("001", channelDto.Map[0].Channel); + Assert.Equal("BBC ONE South", channelDto.Map[0].ProvderCallsign); + Assert.Equal("1", channelDto.Map[0].LogicalChannelNumber); + Assert.Equal("providerCallsign", channelDto.Map[0].MatchType); + } + } +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/headends_response.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/headends_response.json new file mode 100644 index 00000000000..a1a96cf87ac --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/headends_response.json @@ -0,0 +1,113 @@ +[ + { + "headend": "CA00053", + "transport": "Cable", + "location": "Beverly Hills", + "lineups": [ + { + "name": "Time Warner Cable - Cable", + "lineup": "USA-CA00053-DEFAULT", + "uri": "/20141201/lineups/USA-CA00053-DEFAULT" + }, + { + "name": "Time Warner Cable - Digital", + "lineup": "USA-CA00053-X", + "uri": "/20141201/lineups/USA-CA00053-X" + } + ] + }, + { + "headend": "CA61222", + "transport": "Cable", + "location": "Beverly Hills", + "lineups": [ + { + "name": "Mulholland Estates - Cable", + "lineup": "USA-CA61222-DEFAULT", + "uri": "/20141201/lineups/USA-CA61222-DEFAULT" + } + ] + }, + { + "headend": "CA66511", + "transport": "Cable", + "location": "Los Angeles", + "lineups": [ + { + "name": "AT&T U-verse TV - Digital", + "lineup": "USA-CA66511-X", + "uri": "/20141201/lineups/USA-CA66511-X" + } + ] + }, + { + "headend": "CA67309", + "transport": "Cable", + "location": "Westchester", + "lineups": [ + { + "name": "Time Warner Cable Sherman Oaks - Cable", + "lineup": "USA-CA67309-DEFAULT", + "uri": "/20141201/lineups/USA-CA67309-DEFAULT" + }, + { + "name": "Time Warner Cable Sherman Oaks - Digital", + "lineup": "USA-CA67309-X", + "uri": "/20141201/lineups/USA-CA67309-X" + } + ] + }, + { + "headend": "CA67310", + "transport": "Cable", + "location": "Eagle Rock", + "lineups": [ + { + "name": "Time Warner Cable City of Los Angeles - Cable", + "lineup": "USA-CA67310-DEFAULT", + "uri": "/20141201/lineups/USA-CA67310-DEFAULT" + }, + { + "name": "Time Warner Cable City of Los Angeles - Digital", + "lineup": "USA-CA67310-X", + "uri": "/20141201/lineups/USA-CA67310-X" + } + ] + }, + { + "headend": "DISH803", + "transport": "Satellite", + "location": "Los Angeles", + "lineups": [ + { + "name": "DISH Los Angeles - Satellite", + "lineup": "USA-DISH803-DEFAULT", + "uri": "/20141201/lineups/USA-DISH803-DEFAULT" + } + ] + }, + { + "headend": "DITV803", + "transport": "Satellite", + "location": "Los Angeles", + "lineups": [ + { + "name": "DIRECTV Los Angeles - Satellite", + "lineup": "USA-DITV803-DEFAULT", + "uri": "/20141201/lineups/USA-DITV803-DEFAULT" + } + ] + }, + { + "headend": "90210", + "transport": "Antenna", + "location": "90210", + "lineups": [ + { + "name": "Antenna", + "lineup": "USA-OTA-90210", + "uri": "/20141201/lineups/USA-OTA-90210" + } + ] + } +] diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/lineup_response.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/lineup_response.json new file mode 100644 index 00000000000..5c5e36e3018 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/lineup_response.json @@ -0,0 +1,18 @@ +{ + "map": [ + { + "stationID": "24326", + "channel": "001", + "providerCallsign": "BBC ONE South", + "logicalChannelNumber": "1", + "matchType": "providerCallsign" + }, + { + "stationID": "17154", + "channel": "002", + "providerCallsign": "BBC TWO", + "logicalChannelNumber": "2", + "matchType": "providerCallsign" + } + ] +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/lineups_response.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/lineups_response.json new file mode 100644 index 00000000000..257b682eef7 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/lineups_response.json @@ -0,0 +1,40 @@ +{ + "code": 0, + "serverID": "20141201.web.1", + "datetime": "2015-04-17T14:22:17Z", + "lineups": [ + { + "lineup": "GBR-0001317-DEFAULT", + "name": "Freeview - Carlton - LWT (Southeast)", + "transport": "DVB-T", + "location": "London", + "uri": "/20141201/lineups/GBR-0001317-DEFAULT" + }, + { + "lineup": "USA-IL57303-X", + "name": "Comcast Waukegan/Lake Forest Area - Digital", + "transport": "Cable", + "location": "Lake Forest", + "uri": "/20141201/lineups/USA-IL57303-X" + }, + { + "lineup": "USA-NY67791-X", + "name": "Verizon Fios Queens - Digital", + "transport": "Cable", + "location": "Fresh Meadows", + "uri": "/20141201/lineups/USA-NY67791-X" + }, + { + "lineup": "USA-OTA-60030", + "name": "Local Over the Air Broadcast", + "transport": "Antenna", + "location": "60030", + "uri": "/20141201/lineups/USA-OTA-60030" + }, + { + "lineup": "USA-WI61859-DEFAULT", + "name": "DELETED LINEUP", + "isDeleted": true + } + ] +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/metadata_programs_response.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/metadata_programs_response.json new file mode 100644 index 00000000000..b75e777b481 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/metadata_programs_response.json @@ -0,0 +1,51 @@ +[ + { + "programID": "SH00712240", + "data": [ + { + "width": "135", + "height": "180", + "uri": "assets/p282288_b_v2_aa.jpg", + "size": "Sm", + "aspect": "3x4", + "category": "Banner-L3", + "text": "yes", + "primary": "true", + "tier": "Series" + }, + { + "width": "720", + "height": "540", + "uri": "assets/p282288_b_h6_aa.jpg", + "size": "Lg", + "aspect": "4x3", + "category": "Banner-L3", + "text": "yes", + "primary": "true", + "tier": "Series" + }, + { + "width": "960", + "height": "1440", + "uri": "assets/p282288_b_v8_aa.jpg", + "size": "Ms", + "aspect": "2x3", + "category": "Banner-L3", + "text": "yes", + "primary": "true", + "tier": "Series" + }, + { + "width": "180", + "height": "135", + "uri": "assets/p282288_b_h5_aa.jpg", + "size": "Sm", + "aspect": "4x3", + "category": "Banner-L3", + "text": "yes", + "primary": "true", + "tier": "Series" + } + ] + } +] diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/programs_response.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/programs_response.json new file mode 100644 index 00000000000..154c02f9734 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/programs_response.json @@ -0,0 +1,245 @@ +[ + { + "programID": "EP000000060003", + "titles": [{ + "title120": "'Allo 'Allo!" + }], + "eventDetails": { + "subType": "Series" + }, + "descriptions": { + "description1000": [{ + "descriptionLanguage": "en", + "description": "A disguised British Intelligence officer is sent to help the airmen." + }] + }, + "originalAirDate": "1985-11-04", + "genres": ["Sitcom"], + "episodeTitle150": "The Poloceman Cometh", + "metadata": [{ + "Gracenote": { + "season": 2, + "episode": 3 + } + }], + "cast": [{ + "personId": "383774", + "nameId": "392649", + "name": "Gorden Kaye", + "role": "Actor", + "billingOrder": "01" + }, { + "personId": "246840", + "nameId": "250387", + "name": "Carmen Silvera", + "role": "Actor", + "billingOrder": "02" + }, { + "personId": "376955", + "nameId": "385830", + "name": "Rose Hill", + "role": "Actor", + "billingOrder": "03" + }, { + "personId": "259773", + "nameId": "263340", + "name": "Vicki Michelle", + "role": "Actor", + "billingOrder": "04" + }, { + "personId": "353113", + "nameId": "361987", + "name": "Kirsten Cooke", + "role": "Actor", + "billingOrder": "05" + }, { + "personId": "77787", + "nameId": "77787", + "name": "Richard Marner", + "role": "Actor", + "billingOrder": "06" + }, { + "personId": "230921", + "nameId": "234193", + "name": "Guy Siner", + "role": "Actor", + "billingOrder": "07" + }, { + "personId": "374934", + "nameId": "383809", + "name": "Kim Hartman", + "role": "Actor", + "billingOrder": "08" + }, { + "personId": "369151", + "nameId": "378026", + "name": "Richard Gibson", + "role": "Actor", + "billingOrder": "09" + }, { + "personId": "343690", + "nameId": "352564", + "name": "Arthur Bostrom", + "role": "Actor", + "billingOrder": "10" + }, { + "personId": "352557", + "nameId": "361431", + "name": "John D. Collins", + "role": "Actor", + "billingOrder": "11" + }, { + "personId": "605275", + "nameId": "627734", + "name": "Nicholas Frankau", + "role": "Actor", + "billingOrder": "12" + }, { + "personId": "373394", + "nameId": "382269", + "name": "Jack Haig", + "role": "Actor", + "billingOrder": "13" + }], + "crew": [{ + "personId": "354407", + "nameId": "363281", + "name": "David Croft", + "role": "Director", + "billingOrder": "01" + }, { + "personId": "354407", + "nameId": "363281", + "name": "David Croft", + "role": "Writer", + "billingOrder": "02" + }, { + "personId": "105145", + "nameId": "105145", + "name": "Jeremy Lloyd", + "role": "Writer", + "billingOrder": "03" + }], + "showType": "Series", + "hasImageArtwork": true, + "md5": "Jo5NKxoo44xRvBCAq8QT2A" + }, + { + "programID": "EP000000510142", + "titles": [{ + "title120": "A Different World" + }], + "eventDetails": { + "subType": "Series" + }, + "descriptions": { + "description1000": [{ + "descriptionLanguage": "en", + "description": "Whitley and Dwayne tell new students about their honeymoon in Los Angeles." + }] + }, + "originalAirDate": "1992-09-24", + "genres": ["Sitcom"], + "episodeTitle150": "Honeymoon in L.A.", + "metadata": [{ + "Gracenote": { + "season": 6, + "episode": 1 + } + }], + "cast": [{ + "personId": "700", + "nameId": "700", + "name": "Jasmine Guy", + "role": "Actor", + "billingOrder": "01" + }, { + "personId": "729", + "nameId": "729", + "name": "Kadeem Hardison", + "role": "Actor", + "billingOrder": "02" + }, { + "personId": "120", + "nameId": "120", + "name": "Darryl M. Bell", + "role": "Actor", + "billingOrder": "03" + }, { + "personId": "1729", + "nameId": "1729", + "name": "Cree Summer", + "role": "Actor", + "billingOrder": "04" + }, { + "personId": "217", + "nameId": "217", + "name": "Charnele Brown", + "role": "Actor", + "billingOrder": "05" + }, { + "personId": "1811", + "nameId": "1811", + "name": "Glynn Turman", + "role": "Actor", + "billingOrder": "06" + }, { + "personId": "1232", + "nameId": "1232", + "name": "Lou Myers", + "role": "Actor", + "billingOrder": "07" + }, { + "personId": "1363", + "nameId": "1363", + "name": "Jada Pinkett", + "role": "Guest Star", + "billingOrder": "08" + }, { + "personId": "222967", + "nameId": "225536", + "name": "Ajai Sanders", + "role": "Guest Star", + "billingOrder": "09" + }, { + "personId": "181744", + "nameId": "183292", + "name": "Karen Malina White", + "role": "Guest Star", + "billingOrder": "10" + }, { + "personId": "305017", + "nameId": "318897", + "name": "Patrick Y. Malone", + "role": "Guest Star", + "billingOrder": "11" + }, { + "personId": "9841", + "nameId": "9841", + "name": "Bumper Robinson", + "role": "Guest Star", + "billingOrder": "12" + }, { + "personId": "426422", + "nameId": "435297", + "name": "Sister Souljah", + "role": "Guest Star", + "billingOrder": "13" + }, { + "personId": "25", + "nameId": "25", + "name": "Debbie Allen", + "role": "Guest Star", + "billingOrder": "14" + }, { + "personId": "668", + "nameId": "668", + "name": "Gilbert Gottfried", + "role": "Guest Star", + "billingOrder": "15" + }], + "showType": "Series", + "hasImageArtwork": true, + "md5": "P5kz0QmCeYxIA+yL0H4DWw" + } +] diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/schedules_request.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/schedules_request.json new file mode 100644 index 00000000000..72248921ae8 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/schedules_request.json @@ -0,0 +1,16 @@ +[ + { + "stationID": "20454", + "date": [ + "2015-03-13", + "2015-03-17" + ] + }, + { + "stationID": "10021", + "date": [ + "2015-03-12", + "2015-03-13" + ] + } +] diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/schedules_response.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/schedules_response.json new file mode 100644 index 00000000000..f474f3aff2e --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/schedules_response.json @@ -0,0 +1,35 @@ +[ + { + "stationID": "20454", + "programs": [ + { + "programID": "SH005371070000", + "airDateTime": "2015-03-03T00:00:00Z", + "duration": 1800, + "md5": "Sy8HEMBPcuiAx3FBukUhKQ", + "new": true, + "audioProperties": [ + "stereo", + "cc" + ], + "videoProperties": [ + "hdtv" + ] + }, + { + "programID": "EP000014577244", + "airDateTime": "2015-03-03T00:30:00Z", + "duration": 1800, + "md5": "25DNXVXO192JI7Y9vSW9lQ", + "new": true, + "audioProperties": [ + "stereo", + "cc" + ], + "videoProperties": [ + "hdtv" + ] + } + ] + } +] diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/token_live_response.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/token_live_response.json new file mode 100644 index 00000000000..73b0a54c4e4 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/token_live_response.json @@ -0,0 +1,7 @@ +{ + "code": 0, + "message": "OK", + "serverID": "AWS-SD-web.1", + "datetime": "2016-08-23T13:55:25Z", + "token": "f3fca79989cafe7dead71beefedc812b" +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/token_offline_response.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/token_offline_response.json new file mode 100644 index 00000000000..b630c240415 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/token_offline_response.json @@ -0,0 +1,8 @@ +{ + "response": "SERVICE_OFFLINE", + "code": 3000, + "serverID": "20141201.web.1", + "message": "Server offline for maintenance.", + "datetime": "2015-04-23T00:03:32Z", + "token": "CAFEDEADBEEFCAFEDEADBEEFCAFEDEADBEEFCAFE" +} From 0587b539ec899c35b5bea73801d5007575f86b94 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Fri, 3 Sep 2021 13:36:07 -0600 Subject: [PATCH 036/278] Suggestions from review --- .../LiveTv/Listings/SchedulesDirect.cs | 25 +- ...llyfin.Server.Implementations.Tests.csproj | 3 - .../SchedulesDirectDeserializeTests.cs | 25 +- .../TestData/headends_response.json | 113 -------- .../TestData/lineup_response.json | 18 -- .../TestData/lineups_response.json | 40 --- .../TestData/metadata_programs_response.json | 51 ---- .../TestData/programs_response.json | 245 ------------------ .../TestData/schedules_request.json | 16 -- .../TestData/schedules_response.json | 35 --- .../TestData/token_live_response.json | 7 - .../TestData/token_offline_response.json | 8 - .../SchedulesDirect/headends_response.json | 1 + .../SchedulesDirect/lineup_response.json | 1 + .../SchedulesDirect/lineups_response.json | 1 + .../metadata_programs_response.json | 1 + .../SchedulesDirect/programs_response.json | 1 + .../SchedulesDirect/schedules_request.json | 1 + .../SchedulesDirect/schedules_response.json | 1 + .../SchedulesDirect/token_live_response.json | 1 + .../token_offline_response.json | 1 + 21 files changed, 40 insertions(+), 555 deletions(-) delete mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/headends_response.json delete mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/lineup_response.json delete mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/lineups_response.json delete mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/metadata_programs_response.json delete mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/programs_response.json delete mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/schedules_request.json delete mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/schedules_response.json delete mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/token_live_response.json delete mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/token_offline_response.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/headends_response.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineup_response.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineups_response.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/metadata_programs_response.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/programs_response.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_request.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_response.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_live_response.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_offline_response.json diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index e8562b8c577..6686b625f46 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -9,6 +9,7 @@ using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Net.Http.Json; using System.Net.Mime; using System.Text; @@ -113,18 +114,29 @@ namespace Emby.Server.Implementations.LiveTv.Listings using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false); await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var dailySchedules = await JsonSerializer.DeserializeAsync>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false); - _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules!.Count, channelId); + if (dailySchedules == null) + { + return Array.Empty(); + } + + _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId); using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs"); programRequestOptions.Headers.TryAddWithoutValidation("token", token); var programIds = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct(); - programRequestOptions.Content = new StringContent(JsonSerializer.Serialize(programIds), Encoding.UTF8, MediaTypeNames.Application.Json); + programRequestOptions.Content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(programIds, _jsonOptions)); + programRequestOptions.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false); await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var programDetails = await JsonSerializer.DeserializeAsync>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false); - var programDict = programDetails!.ToDictionary(p => p.ProgramId, y => y); + if (programDetails == null) + { + return Array.Empty(); + } + + var programDict = programDetails.ToDictionary(p => p.ProgramId, y => y); var programIdsWithImages = programDetails .Where(p => p.HasImageArtwork).Select(p => p.ProgramId) @@ -775,7 +787,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false); await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var root = await JsonSerializer.DeserializeAsync(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); - _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root!.Map.Count); + if (root == null) + { + return new List(); + } + + _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.Map.Count); _logger.LogInformation("Mapping Stations to Channel"); var allStations = root.Stations; diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index e02e357ca07..9b6ab7bdf5b 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -16,9 +16,6 @@ PreserveNewest - - PreserveNewest - diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs index 84ac3ea4774..ab08e22ab1b 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs @@ -23,7 +23,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect [Fact] public void Deserialize_Token_Response_Live_Success() { - var bytes = File.ReadAllBytes("LiveTv/SchedulesDirect/TestData/token_live_response.json"); + var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/token_live_response.json"); var tokenDto = JsonSerializer.Deserialize(bytes); Assert.NotNull(tokenDto); @@ -40,7 +40,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect [Fact] public void Deserialize_Token_Response_Offline_Success() { - var bytes = File.ReadAllBytes("LiveTv/SchedulesDirect/TestData/token_offline_response.json"); + var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/token_offline_response.json"); var tokenDto = JsonSerializer.Deserialize(bytes); Assert.NotNull(tokenDto); @@ -58,7 +58,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect [Fact] public void Serialize_Schedule_Request_Success() { - var expectedString = File.ReadAllText("LiveTv/SchedulesDirect/TestData/schedules_request.json").Trim(); + var expectedString = File.ReadAllText("Test Data/SchedulesDirect/schedules_request.json").Trim(); var requestObject = new RequestScheduleForChannelDto[] { @@ -82,12 +82,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect } }; - var jsonOptions = new JsonSerializerOptions(_jsonOptions) - { - WriteIndented = true - }; - - var requestString = JsonSerializer.Serialize(requestObject, jsonOptions); + var requestString = JsonSerializer.Serialize(requestObject, _jsonOptions); Assert.Equal(expectedString, requestString); } @@ -97,7 +92,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect [Fact] public void Deserialize_Schedule_Response_Success() { - var bytes = File.ReadAllBytes("LiveTv/SchedulesDirect/TestData/schedules_response.json"); + var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/schedules_response.json"); var days = JsonSerializer.Deserialize>(bytes); Assert.NotNull(days); @@ -125,7 +120,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect [Fact] public void Deserialize_Program_Response_Success() { - var bytes = File.ReadAllBytes("LiveTv/SchedulesDirect/TestData/programs_response.json"); + var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/programs_response.json"); var programDtos = JsonSerializer.Deserialize>(bytes); Assert.NotNull(programDtos); @@ -162,7 +157,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect [Fact] public void Deserialize_Metadata_Programs_Response_Success() { - var bytes = File.ReadAllBytes("LiveTv/SchedulesDirect/TestData/metadata_programs_response.json"); + var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/metadata_programs_response.json"); var showImagesDtos = JsonSerializer.Deserialize>(bytes); Assert.NotNull(showImagesDtos); @@ -186,7 +181,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect [Fact] public void Deserialize_Headends_Response_Success() { - var bytes = File.ReadAllBytes("LiveTv/SchedulesDirect/TestData/headends_response.json"); + var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/headends_response.json"); var headendsDtos = JsonSerializer.Deserialize>(bytes); Assert.NotNull(headendsDtos); @@ -206,7 +201,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect [Fact] public void Deserialize_Lineups_Response_Success() { - var bytes = File.ReadAllBytes("LiveTv/SchedulesDirect/TestData/lineups_response.json"); + var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/lineups_response.json"); var lineupsDto = JsonSerializer.Deserialize(bytes); Assert.NotNull(lineupsDto); @@ -230,7 +225,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect [Fact] public void Deserialize_Lineup_Response_Success() { - var bytes = File.ReadAllBytes("LiveTv/SchedulesDirect/TestData/lineup_response.json"); + var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/lineup_response.json"); var channelDto = JsonSerializer.Deserialize(bytes); Assert.NotNull(channelDto); diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/headends_response.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/headends_response.json deleted file mode 100644 index a1a96cf87ac..00000000000 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/headends_response.json +++ /dev/null @@ -1,113 +0,0 @@ -[ - { - "headend": "CA00053", - "transport": "Cable", - "location": "Beverly Hills", - "lineups": [ - { - "name": "Time Warner Cable - Cable", - "lineup": "USA-CA00053-DEFAULT", - "uri": "/20141201/lineups/USA-CA00053-DEFAULT" - }, - { - "name": "Time Warner Cable - Digital", - "lineup": "USA-CA00053-X", - "uri": "/20141201/lineups/USA-CA00053-X" - } - ] - }, - { - "headend": "CA61222", - "transport": "Cable", - "location": "Beverly Hills", - "lineups": [ - { - "name": "Mulholland Estates - Cable", - "lineup": "USA-CA61222-DEFAULT", - "uri": "/20141201/lineups/USA-CA61222-DEFAULT" - } - ] - }, - { - "headend": "CA66511", - "transport": "Cable", - "location": "Los Angeles", - "lineups": [ - { - "name": "AT&T U-verse TV - Digital", - "lineup": "USA-CA66511-X", - "uri": "/20141201/lineups/USA-CA66511-X" - } - ] - }, - { - "headend": "CA67309", - "transport": "Cable", - "location": "Westchester", - "lineups": [ - { - "name": "Time Warner Cable Sherman Oaks - Cable", - "lineup": "USA-CA67309-DEFAULT", - "uri": "/20141201/lineups/USA-CA67309-DEFAULT" - }, - { - "name": "Time Warner Cable Sherman Oaks - Digital", - "lineup": "USA-CA67309-X", - "uri": "/20141201/lineups/USA-CA67309-X" - } - ] - }, - { - "headend": "CA67310", - "transport": "Cable", - "location": "Eagle Rock", - "lineups": [ - { - "name": "Time Warner Cable City of Los Angeles - Cable", - "lineup": "USA-CA67310-DEFAULT", - "uri": "/20141201/lineups/USA-CA67310-DEFAULT" - }, - { - "name": "Time Warner Cable City of Los Angeles - Digital", - "lineup": "USA-CA67310-X", - "uri": "/20141201/lineups/USA-CA67310-X" - } - ] - }, - { - "headend": "DISH803", - "transport": "Satellite", - "location": "Los Angeles", - "lineups": [ - { - "name": "DISH Los Angeles - Satellite", - "lineup": "USA-DISH803-DEFAULT", - "uri": "/20141201/lineups/USA-DISH803-DEFAULT" - } - ] - }, - { - "headend": "DITV803", - "transport": "Satellite", - "location": "Los Angeles", - "lineups": [ - { - "name": "DIRECTV Los Angeles - Satellite", - "lineup": "USA-DITV803-DEFAULT", - "uri": "/20141201/lineups/USA-DITV803-DEFAULT" - } - ] - }, - { - "headend": "90210", - "transport": "Antenna", - "location": "90210", - "lineups": [ - { - "name": "Antenna", - "lineup": "USA-OTA-90210", - "uri": "/20141201/lineups/USA-OTA-90210" - } - ] - } -] diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/lineup_response.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/lineup_response.json deleted file mode 100644 index 5c5e36e3018..00000000000 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/lineup_response.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "map": [ - { - "stationID": "24326", - "channel": "001", - "providerCallsign": "BBC ONE South", - "logicalChannelNumber": "1", - "matchType": "providerCallsign" - }, - { - "stationID": "17154", - "channel": "002", - "providerCallsign": "BBC TWO", - "logicalChannelNumber": "2", - "matchType": "providerCallsign" - } - ] -} diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/lineups_response.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/lineups_response.json deleted file mode 100644 index 257b682eef7..00000000000 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/lineups_response.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "code": 0, - "serverID": "20141201.web.1", - "datetime": "2015-04-17T14:22:17Z", - "lineups": [ - { - "lineup": "GBR-0001317-DEFAULT", - "name": "Freeview - Carlton - LWT (Southeast)", - "transport": "DVB-T", - "location": "London", - "uri": "/20141201/lineups/GBR-0001317-DEFAULT" - }, - { - "lineup": "USA-IL57303-X", - "name": "Comcast Waukegan/Lake Forest Area - Digital", - "transport": "Cable", - "location": "Lake Forest", - "uri": "/20141201/lineups/USA-IL57303-X" - }, - { - "lineup": "USA-NY67791-X", - "name": "Verizon Fios Queens - Digital", - "transport": "Cable", - "location": "Fresh Meadows", - "uri": "/20141201/lineups/USA-NY67791-X" - }, - { - "lineup": "USA-OTA-60030", - "name": "Local Over the Air Broadcast", - "transport": "Antenna", - "location": "60030", - "uri": "/20141201/lineups/USA-OTA-60030" - }, - { - "lineup": "USA-WI61859-DEFAULT", - "name": "DELETED LINEUP", - "isDeleted": true - } - ] -} diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/metadata_programs_response.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/metadata_programs_response.json deleted file mode 100644 index b75e777b481..00000000000 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/metadata_programs_response.json +++ /dev/null @@ -1,51 +0,0 @@ -[ - { - "programID": "SH00712240", - "data": [ - { - "width": "135", - "height": "180", - "uri": "assets/p282288_b_v2_aa.jpg", - "size": "Sm", - "aspect": "3x4", - "category": "Banner-L3", - "text": "yes", - "primary": "true", - "tier": "Series" - }, - { - "width": "720", - "height": "540", - "uri": "assets/p282288_b_h6_aa.jpg", - "size": "Lg", - "aspect": "4x3", - "category": "Banner-L3", - "text": "yes", - "primary": "true", - "tier": "Series" - }, - { - "width": "960", - "height": "1440", - "uri": "assets/p282288_b_v8_aa.jpg", - "size": "Ms", - "aspect": "2x3", - "category": "Banner-L3", - "text": "yes", - "primary": "true", - "tier": "Series" - }, - { - "width": "180", - "height": "135", - "uri": "assets/p282288_b_h5_aa.jpg", - "size": "Sm", - "aspect": "4x3", - "category": "Banner-L3", - "text": "yes", - "primary": "true", - "tier": "Series" - } - ] - } -] diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/programs_response.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/programs_response.json deleted file mode 100644 index 154c02f9734..00000000000 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/programs_response.json +++ /dev/null @@ -1,245 +0,0 @@ -[ - { - "programID": "EP000000060003", - "titles": [{ - "title120": "'Allo 'Allo!" - }], - "eventDetails": { - "subType": "Series" - }, - "descriptions": { - "description1000": [{ - "descriptionLanguage": "en", - "description": "A disguised British Intelligence officer is sent to help the airmen." - }] - }, - "originalAirDate": "1985-11-04", - "genres": ["Sitcom"], - "episodeTitle150": "The Poloceman Cometh", - "metadata": [{ - "Gracenote": { - "season": 2, - "episode": 3 - } - }], - "cast": [{ - "personId": "383774", - "nameId": "392649", - "name": "Gorden Kaye", - "role": "Actor", - "billingOrder": "01" - }, { - "personId": "246840", - "nameId": "250387", - "name": "Carmen Silvera", - "role": "Actor", - "billingOrder": "02" - }, { - "personId": "376955", - "nameId": "385830", - "name": "Rose Hill", - "role": "Actor", - "billingOrder": "03" - }, { - "personId": "259773", - "nameId": "263340", - "name": "Vicki Michelle", - "role": "Actor", - "billingOrder": "04" - }, { - "personId": "353113", - "nameId": "361987", - "name": "Kirsten Cooke", - "role": "Actor", - "billingOrder": "05" - }, { - "personId": "77787", - "nameId": "77787", - "name": "Richard Marner", - "role": "Actor", - "billingOrder": "06" - }, { - "personId": "230921", - "nameId": "234193", - "name": "Guy Siner", - "role": "Actor", - "billingOrder": "07" - }, { - "personId": "374934", - "nameId": "383809", - "name": "Kim Hartman", - "role": "Actor", - "billingOrder": "08" - }, { - "personId": "369151", - "nameId": "378026", - "name": "Richard Gibson", - "role": "Actor", - "billingOrder": "09" - }, { - "personId": "343690", - "nameId": "352564", - "name": "Arthur Bostrom", - "role": "Actor", - "billingOrder": "10" - }, { - "personId": "352557", - "nameId": "361431", - "name": "John D. Collins", - "role": "Actor", - "billingOrder": "11" - }, { - "personId": "605275", - "nameId": "627734", - "name": "Nicholas Frankau", - "role": "Actor", - "billingOrder": "12" - }, { - "personId": "373394", - "nameId": "382269", - "name": "Jack Haig", - "role": "Actor", - "billingOrder": "13" - }], - "crew": [{ - "personId": "354407", - "nameId": "363281", - "name": "David Croft", - "role": "Director", - "billingOrder": "01" - }, { - "personId": "354407", - "nameId": "363281", - "name": "David Croft", - "role": "Writer", - "billingOrder": "02" - }, { - "personId": "105145", - "nameId": "105145", - "name": "Jeremy Lloyd", - "role": "Writer", - "billingOrder": "03" - }], - "showType": "Series", - "hasImageArtwork": true, - "md5": "Jo5NKxoo44xRvBCAq8QT2A" - }, - { - "programID": "EP000000510142", - "titles": [{ - "title120": "A Different World" - }], - "eventDetails": { - "subType": "Series" - }, - "descriptions": { - "description1000": [{ - "descriptionLanguage": "en", - "description": "Whitley and Dwayne tell new students about their honeymoon in Los Angeles." - }] - }, - "originalAirDate": "1992-09-24", - "genres": ["Sitcom"], - "episodeTitle150": "Honeymoon in L.A.", - "metadata": [{ - "Gracenote": { - "season": 6, - "episode": 1 - } - }], - "cast": [{ - "personId": "700", - "nameId": "700", - "name": "Jasmine Guy", - "role": "Actor", - "billingOrder": "01" - }, { - "personId": "729", - "nameId": "729", - "name": "Kadeem Hardison", - "role": "Actor", - "billingOrder": "02" - }, { - "personId": "120", - "nameId": "120", - "name": "Darryl M. Bell", - "role": "Actor", - "billingOrder": "03" - }, { - "personId": "1729", - "nameId": "1729", - "name": "Cree Summer", - "role": "Actor", - "billingOrder": "04" - }, { - "personId": "217", - "nameId": "217", - "name": "Charnele Brown", - "role": "Actor", - "billingOrder": "05" - }, { - "personId": "1811", - "nameId": "1811", - "name": "Glynn Turman", - "role": "Actor", - "billingOrder": "06" - }, { - "personId": "1232", - "nameId": "1232", - "name": "Lou Myers", - "role": "Actor", - "billingOrder": "07" - }, { - "personId": "1363", - "nameId": "1363", - "name": "Jada Pinkett", - "role": "Guest Star", - "billingOrder": "08" - }, { - "personId": "222967", - "nameId": "225536", - "name": "Ajai Sanders", - "role": "Guest Star", - "billingOrder": "09" - }, { - "personId": "181744", - "nameId": "183292", - "name": "Karen Malina White", - "role": "Guest Star", - "billingOrder": "10" - }, { - "personId": "305017", - "nameId": "318897", - "name": "Patrick Y. Malone", - "role": "Guest Star", - "billingOrder": "11" - }, { - "personId": "9841", - "nameId": "9841", - "name": "Bumper Robinson", - "role": "Guest Star", - "billingOrder": "12" - }, { - "personId": "426422", - "nameId": "435297", - "name": "Sister Souljah", - "role": "Guest Star", - "billingOrder": "13" - }, { - "personId": "25", - "nameId": "25", - "name": "Debbie Allen", - "role": "Guest Star", - "billingOrder": "14" - }, { - "personId": "668", - "nameId": "668", - "name": "Gilbert Gottfried", - "role": "Guest Star", - "billingOrder": "15" - }], - "showType": "Series", - "hasImageArtwork": true, - "md5": "P5kz0QmCeYxIA+yL0H4DWw" - } -] diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/schedules_request.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/schedules_request.json deleted file mode 100644 index 72248921ae8..00000000000 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/schedules_request.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "stationID": "20454", - "date": [ - "2015-03-13", - "2015-03-17" - ] - }, - { - "stationID": "10021", - "date": [ - "2015-03-12", - "2015-03-13" - ] - } -] diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/schedules_response.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/schedules_response.json deleted file mode 100644 index f474f3aff2e..00000000000 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/schedules_response.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - { - "stationID": "20454", - "programs": [ - { - "programID": "SH005371070000", - "airDateTime": "2015-03-03T00:00:00Z", - "duration": 1800, - "md5": "Sy8HEMBPcuiAx3FBukUhKQ", - "new": true, - "audioProperties": [ - "stereo", - "cc" - ], - "videoProperties": [ - "hdtv" - ] - }, - { - "programID": "EP000014577244", - "airDateTime": "2015-03-03T00:30:00Z", - "duration": 1800, - "md5": "25DNXVXO192JI7Y9vSW9lQ", - "new": true, - "audioProperties": [ - "stereo", - "cc" - ], - "videoProperties": [ - "hdtv" - ] - } - ] - } -] diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/token_live_response.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/token_live_response.json deleted file mode 100644 index 73b0a54c4e4..00000000000 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/token_live_response.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "code": 0, - "message": "OK", - "serverID": "AWS-SD-web.1", - "datetime": "2016-08-23T13:55:25Z", - "token": "f3fca79989cafe7dead71beefedc812b" -} diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/token_offline_response.json b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/token_offline_response.json deleted file mode 100644 index b630c240415..00000000000 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/TestData/token_offline_response.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "response": "SERVICE_OFFLINE", - "code": 3000, - "serverID": "20141201.web.1", - "message": "Server offline for maintenance.", - "datetime": "2015-04-23T00:03:32Z", - "token": "CAFEDEADBEEFCAFEDEADBEEFCAFEDEADBEEFCAFE" -} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/headends_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/headends_response.json new file mode 100644 index 00000000000..015afeecc0b --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/headends_response.json @@ -0,0 +1 @@ +[{"headend":"CA00053","transport":"Cable","location":"Beverly Hills","lineups":[{"name":"Time Warner Cable - Cable","lineup":"USA-CA00053-DEFAULT","uri":"/20141201/lineups/USA-CA00053-DEFAULT"},{"name":"Time Warner Cable - Digital","lineup":"USA-CA00053-X","uri":"/20141201/lineups/USA-CA00053-X"}]},{"headend":"CA61222","transport":"Cable","location":"Beverly Hills","lineups":[{"name":"Mulholland Estates - Cable","lineup":"USA-CA61222-DEFAULT","uri":"/20141201/lineups/USA-CA61222-DEFAULT"}]},{"headend":"CA66511","transport":"Cable","location":"Los Angeles","lineups":[{"name":"AT&T U-verse TV - Digital","lineup":"USA-CA66511-X","uri":"/20141201/lineups/USA-CA66511-X"}]},{"headend":"CA67309","transport":"Cable","location":"Westchester","lineups":[{"name":"Time Warner Cable Sherman Oaks - Cable","lineup":"USA-CA67309-DEFAULT","uri":"/20141201/lineups/USA-CA67309-DEFAULT"},{"name":"Time Warner Cable Sherman Oaks - Digital","lineup":"USA-CA67309-X","uri":"/20141201/lineups/USA-CA67309-X"}]},{"headend":"CA67310","transport":"Cable","location":"Eagle Rock","lineups":[{"name":"Time Warner Cable City of Los Angeles - Cable","lineup":"USA-CA67310-DEFAULT","uri":"/20141201/lineups/USA-CA67310-DEFAULT"},{"name":"Time Warner Cable City of Los Angeles - Digital","lineup":"USA-CA67310-X","uri":"/20141201/lineups/USA-CA67310-X"}]},{"headend":"DISH803","transport":"Satellite","location":"Los Angeles","lineups":[{"name":"DISH Los Angeles - Satellite","lineup":"USA-DISH803-DEFAULT","uri":"/20141201/lineups/USA-DISH803-DEFAULT"}]},{"headend":"DITV803","transport":"Satellite","location":"Los Angeles","lineups":[{"name":"DIRECTV Los Angeles - Satellite","lineup":"USA-DITV803-DEFAULT","uri":"/20141201/lineups/USA-DITV803-DEFAULT"}]},{"headend":"90210","transport":"Antenna","location":"90210","lineups":[{"name":"Antenna","lineup":"USA-OTA-90210","uri":"/20141201/lineups/USA-OTA-90210"}]}] diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineup_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineup_response.json new file mode 100644 index 00000000000..0720894702e --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineup_response.json @@ -0,0 +1 @@ +{"map":[{"stationID":"24326","channel":"001","providerCallsign":"BBC ONE South","logicalChannelNumber":"1","matchType":"providerCallsign"},{"stationID":"17154","channel":"002","providerCallsign":"BBC TWO","logicalChannelNumber":"2","matchType":"providerCallsign"}]} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineups_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineups_response.json new file mode 100644 index 00000000000..032a84e59ec --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineups_response.json @@ -0,0 +1 @@ +{"code":0,"serverID":"20141201.web.1","datetime":"2015-04-17T14:22:17Z","lineups":[{"lineup":"GBR-0001317-DEFAULT","name":"Freeview - Carlton - LWT (Southeast)","transport":"DVB-T","location":"London","uri":"/20141201/lineups/GBR-0001317-DEFAULT"},{"lineup":"USA-IL57303-X","name":"Comcast Waukegan/Lake Forest Area - Digital","transport":"Cable","location":"Lake Forest","uri":"/20141201/lineups/USA-IL57303-X"},{"lineup":"USA-NY67791-X","name":"Verizon Fios Queens - Digital","transport":"Cable","location":"Fresh Meadows","uri":"/20141201/lineups/USA-NY67791-X"},{"lineup":"USA-OTA-60030","name":"Local Over the Air Broadcast","transport":"Antenna","location":"60030","uri":"/20141201/lineups/USA-OTA-60030"},{"lineup":"USA-WI61859-DEFAULT","name":"DELETED LINEUP","isDeleted":true}]} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/metadata_programs_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/metadata_programs_response.json new file mode 100644 index 00000000000..78166f09a4e --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/metadata_programs_response.json @@ -0,0 +1 @@ +[{"programID":"SH00712240","data":[{"width":"135","height":"180","uri":"assets/p282288_b_v2_aa.jpg","size":"Sm","aspect":"3x4","category":"Banner-L3","text":"yes","primary":"true","tier":"Series"},{"width":"720","height":"540","uri":"assets/p282288_b_h6_aa.jpg","size":"Lg","aspect":"4x3","category":"Banner-L3","text":"yes","primary":"true","tier":"Series"},{"width":"960","height":"1440","uri":"assets/p282288_b_v8_aa.jpg","size":"Ms","aspect":"2x3","category":"Banner-L3","text":"yes","primary":"true","tier":"Series"},{"width":"180","height":"135","uri":"assets/p282288_b_h5_aa.jpg","size":"Sm","aspect":"4x3","category":"Banner-L3","text":"yes","primary":"true","tier":"Series"}]}] diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/programs_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/programs_response.json new file mode 100644 index 00000000000..fe2a9443649 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/programs_response.json @@ -0,0 +1 @@ +[{"programID":"EP000000060003","titles":[{"title120":"'Allo 'Allo!"}],"eventDetails":{"subType":"Series"},"descriptions":{"description1000":[{"descriptionLanguage":"en","description":"A disguised British Intelligence officer is sent to help the airmen."}]},"originalAirDate":"1985-11-04","genres":["Sitcom"],"episodeTitle150":"The Poloceman Cometh","metadata":[{"Gracenote":{"season":2,"episode":3}}],"cast":[{"personId":"383774","nameId":"392649","name":"Gorden Kaye","role":"Actor","billingOrder":"01"},{"personId":"246840","nameId":"250387","name":"Carmen Silvera","role":"Actor","billingOrder":"02"},{"personId":"376955","nameId":"385830","name":"Rose Hill","role":"Actor","billingOrder":"03"},{"personId":"259773","nameId":"263340","name":"Vicki Michelle","role":"Actor","billingOrder":"04"},{"personId":"353113","nameId":"361987","name":"Kirsten Cooke","role":"Actor","billingOrder":"05"},{"personId":"77787","nameId":"77787","name":"Richard Marner","role":"Actor","billingOrder":"06"},{"personId":"230921","nameId":"234193","name":"Guy Siner","role":"Actor","billingOrder":"07"},{"personId":"374934","nameId":"383809","name":"Kim Hartman","role":"Actor","billingOrder":"08"},{"personId":"369151","nameId":"378026","name":"Richard Gibson","role":"Actor","billingOrder":"09"},{"personId":"343690","nameId":"352564","name":"Arthur Bostrom","role":"Actor","billingOrder":"10"},{"personId":"352557","nameId":"361431","name":"John D. Collins","role":"Actor","billingOrder":"11"},{"personId":"605275","nameId":"627734","name":"Nicholas Frankau","role":"Actor","billingOrder":"12"},{"personId":"373394","nameId":"382269","name":"Jack Haig","role":"Actor","billingOrder":"13"}],"crew":[{"personId":"354407","nameId":"363281","name":"David Croft","role":"Director","billingOrder":"01"},{"personId":"354407","nameId":"363281","name":"David Croft","role":"Writer","billingOrder":"02"},{"personId":"105145","nameId":"105145","name":"Jeremy Lloyd","role":"Writer","billingOrder":"03"}],"showType":"Series","hasImageArtwork":true,"md5":"Jo5NKxoo44xRvBCAq8QT2A"},{"programID":"EP000000510142","titles":[{"title120":"A Different World"}],"eventDetails":{"subType":"Series"},"descriptions":{"description1000":[{"descriptionLanguage":"en","description":"Whitley and Dwayne tell new students about their honeymoon in Los Angeles."}]},"originalAirDate":"1992-09-24","genres":["Sitcom"],"episodeTitle150":"Honeymoon in L.A.","metadata":[{"Gracenote":{"season":6,"episode":1}}],"cast":[{"personId":"700","nameId":"700","name":"Jasmine Guy","role":"Actor","billingOrder":"01"},{"personId":"729","nameId":"729","name":"Kadeem Hardison","role":"Actor","billingOrder":"02"},{"personId":"120","nameId":"120","name":"Darryl M. Bell","role":"Actor","billingOrder":"03"},{"personId":"1729","nameId":"1729","name":"Cree Summer","role":"Actor","billingOrder":"04"},{"personId":"217","nameId":"217","name":"Charnele Brown","role":"Actor","billingOrder":"05"},{"personId":"1811","nameId":"1811","name":"Glynn Turman","role":"Actor","billingOrder":"06"},{"personId":"1232","nameId":"1232","name":"Lou Myers","role":"Actor","billingOrder":"07"},{"personId":"1363","nameId":"1363","name":"Jada Pinkett","role":"Guest Star","billingOrder":"08"},{"personId":"222967","nameId":"225536","name":"Ajai Sanders","role":"Guest Star","billingOrder":"09"},{"personId":"181744","nameId":"183292","name":"Karen Malina White","role":"Guest Star","billingOrder":"10"},{"personId":"305017","nameId":"318897","name":"Patrick Y. Malone","role":"Guest Star","billingOrder":"11"},{"personId":"9841","nameId":"9841","name":"Bumper Robinson","role":"Guest Star","billingOrder":"12"},{"personId":"426422","nameId":"435297","name":"Sister Souljah","role":"Guest Star","billingOrder":"13"},{"personId":"25","nameId":"25","name":"Debbie Allen","role":"Guest Star","billingOrder":"14"},{"personId":"668","nameId":"668","name":"Gilbert Gottfried","role":"Guest Star","billingOrder":"15"}],"showType":"Series","hasImageArtwork":true,"md5":"P5kz0QmCeYxIA+yL0H4DWw"}] diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_request.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_request.json new file mode 100644 index 00000000000..5ef1bfb1c4f --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_request.json @@ -0,0 +1 @@ +[{"stationID":"20454","date":["2015-03-13","2015-03-17"]},{"stationID":"10021","date":["2015-03-12","2015-03-13"]}] diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_response.json new file mode 100644 index 00000000000..4a97e551720 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_response.json @@ -0,0 +1 @@ +[{"stationID":"20454","programs":[{"programID":"SH005371070000","airDateTime":"2015-03-03T00:00:00Z","duration":1800,"md5":"Sy8HEMBPcuiAx3FBukUhKQ","new":true,"audioProperties":["stereo","cc"],"videoProperties":["hdtv"]},{"programID":"EP000014577244","airDateTime":"2015-03-03T00:30:00Z","duration":1800,"md5":"25DNXVXO192JI7Y9vSW9lQ","new":true,"audioProperties":["stereo","cc"],"videoProperties":["hdtv"]}]}] diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_live_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_live_response.json new file mode 100644 index 00000000000..e5fb64a6fa2 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_live_response.json @@ -0,0 +1 @@ +{"code":0,"message":"OK","serverID":"AWS-SD-web.1","datetime":"2016-08-23T13:55:25Z","token":"f3fca79989cafe7dead71beefedc812b"} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_offline_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_offline_response.json new file mode 100644 index 00000000000..b66a4ed0c4b --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_offline_response.json @@ -0,0 +1 @@ +{"response":"SERVICE_OFFLINE","code":3000,"serverID":"20141201.web.1","message":"Server offline for maintenance.","datetime":"2015-04-23T00:03:32Z","token":"CAFEDEADBEEFCAFEDEADBEEFCAFEDEADBEEFCAFE"} From 04973a489f0ed4ad9d65a7bb6fec0e03d00c6142 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Fri, 3 Sep 2021 13:56:51 -0600 Subject: [PATCH 037/278] use JsonOptions --- .../SchedulesDirectDeserializeTests.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs index ab08e22ab1b..a494d883ecb 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect public void Deserialize_Token_Response_Live_Success() { var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/token_live_response.json"); - var tokenDto = JsonSerializer.Deserialize(bytes); + var tokenDto = JsonSerializer.Deserialize(bytes, _jsonOptions); Assert.NotNull(tokenDto); Assert.Equal(0, tokenDto!.Code); @@ -41,7 +41,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect public void Deserialize_Token_Response_Offline_Success() { var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/token_offline_response.json"); - var tokenDto = JsonSerializer.Deserialize(bytes); + var tokenDto = JsonSerializer.Deserialize(bytes, _jsonOptions); Assert.NotNull(tokenDto); Assert.Equal(3_000, tokenDto!.Code); @@ -93,7 +93,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect public void Deserialize_Schedule_Response_Success() { var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/schedules_response.json"); - var days = JsonSerializer.Deserialize>(bytes); + var days = JsonSerializer.Deserialize>(bytes, _jsonOptions); Assert.NotNull(days); Assert.Equal(1, days!.Count); @@ -121,7 +121,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect public void Deserialize_Program_Response_Success() { var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/programs_response.json"); - var programDtos = JsonSerializer.Deserialize>(bytes); + var programDtos = JsonSerializer.Deserialize>(bytes, _jsonOptions); Assert.NotNull(programDtos); Assert.Equal(2, programDtos!.Count); @@ -158,7 +158,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect public void Deserialize_Metadata_Programs_Response_Success() { var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/metadata_programs_response.json"); - var showImagesDtos = JsonSerializer.Deserialize>(bytes); + var showImagesDtos = JsonSerializer.Deserialize>(bytes, _jsonOptions); Assert.NotNull(showImagesDtos); Assert.Equal(1, showImagesDtos!.Count); @@ -182,7 +182,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect public void Deserialize_Headends_Response_Success() { var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/headends_response.json"); - var headendsDtos = JsonSerializer.Deserialize>(bytes); + var headendsDtos = JsonSerializer.Deserialize>(bytes, _jsonOptions); Assert.NotNull(headendsDtos); Assert.Equal(8, headendsDtos!.Count); @@ -202,7 +202,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect public void Deserialize_Lineups_Response_Success() { var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/lineups_response.json"); - var lineupsDto = JsonSerializer.Deserialize(bytes); + var lineupsDto = JsonSerializer.Deserialize(bytes, _jsonOptions); Assert.NotNull(lineupsDto); Assert.Equal(0, lineupsDto!.Code); @@ -226,7 +226,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect public void Deserialize_Lineup_Response_Success() { var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/lineup_response.json"); - var channelDto = JsonSerializer.Deserialize(bytes); + var channelDto = JsonSerializer.Deserialize(bytes, _jsonOptions); Assert.NotNull(channelDto); Assert.Equal(2, channelDto!.Map.Count); From 7a0a675bd5d668199de1f0763988ca7809b78fb0 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Fri, 3 Sep 2021 14:21:33 -0600 Subject: [PATCH 038/278] Add Jellyfin.Extensions to package publish --- .ci/azure-pipelines-main.yml | 7 +++++++ .ci/azure-pipelines.yml | 3 +++ bump_version | 9 ++++++++- src/Jellyfin.Extensions/Jellyfin.Extensions.csproj | 11 +++++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml index d2c087c14d9..e9b93baafe5 100644 --- a/.ci/azure-pipelines-main.yml +++ b/.ci/azure-pipelines-main.yml @@ -91,3 +91,10 @@ jobs: inputs: targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll' artifactName: 'Jellyfin.Common' + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Artifact Extensions' + condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) + inputs: + targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Jellyfin.Extensions.dll' + artifactName: 'Jellyfin.Extensions' diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 4e8b6557b9c..98d9f3248d8 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -57,6 +57,9 @@ jobs: Common: NugetPackageName: Jellyfin.Common AssemblyFileName: MediaBrowser.Common.dll + Extensions: + NugetPackageName: Jellyfin.Extensions + AssemblyFileName: Jellyfin.Extensions.dll LinuxImage: 'ubuntu-latest' - ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}: diff --git a/bump_version b/bump_version index 8012ec07f1d..f615606e2d2 100755 --- a/bump_version +++ b/bump_version @@ -21,7 +21,14 @@ fi shared_version_file="./SharedVersion.cs" build_file="./build.yaml" # csproj files for nuget packages -jellyfin_subprojects=( MediaBrowser.Common/MediaBrowser.Common.csproj Jellyfin.Data/Jellyfin.Data.csproj MediaBrowser.Controller/MediaBrowser.Controller.csproj MediaBrowser.Model/MediaBrowser.Model.csproj Emby.Naming/Emby.Naming.csproj ) +jellyfin_subprojects=( + MediaBrowser.Common/MediaBrowser.Common.csproj + Jellyfin.Data/Jellyfin.Data.csproj + MediaBrowser.Controller/MediaBrowser.Controller.csproj + MediaBrowser.Model/MediaBrowser.Model.csproj + Emby.Naming/Emby.Naming.csproj + src/Jellyfin.Extensions/Jellyfin.Extensions.csproj +) new_version="$1" diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj index 981b796e03e..2d9ce06fe36 100644 --- a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj +++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj @@ -4,14 +4,25 @@ net5.0 false true + true + true + true + snupkg Jellyfin Contributors + Jellyfin.Extensions + 10.8.0 https://github.com/jellyfin/jellyfin GPL-3.0-only + + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + From cee884613d9cd82e9983b1c2f7f6055542f58ebb Mon Sep 17 00:00:00 2001 From: Mayur Panchal Date: Mon, 6 Sep 2021 20:55:00 +1000 Subject: [PATCH 039/278] Update Intel Compute Runtime Resources --- Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3190fec5c58..88bc8504f15 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,10 +18,10 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" # https://github.com/intel/compute-runtime/releases -ARG GMMLIB_VERSION=20.3.2 -ARG IGC_VERSION=1.0.5435 -ARG NEO_VERSION=20.46.18421 -ARG LEVEL_ZERO_VERSION=1.0.18421 +ARG GMMLIB_VERSION=21.2.1 +ARG IGC_VERSION=1.0.8517 +ARG NEO_VERSION=21.35.20826 +ARG LEVEL_ZERO_VERSION=1.2.20826 # Install dependencies: # mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding. From 4a4a291aa93037501f4e528452add96eac1897fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Sep 2021 12:00:53 +0000 Subject: [PATCH 040/278] Bump Swashbuckle.AspNetCore from 6.1.5 to 6.2.1 Bumps [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) from 6.1.5 to 6.2.1. - [Release notes](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases) - [Commits](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v6.1.5...v6.2.1) --- updated-dependencies: - dependency-name: Swashbuckle.AspNetCore dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 669925198b4..8f57879e1c5 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -16,7 +16,7 @@ - + From 9e96f3613172116caaa3921f0931260557ae379e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Sep 2021 12:00:55 +0000 Subject: [PATCH 041/278] Bump prometheus-net.DotNetRuntime from 4.2.0 to 4.2.1 Bumps [prometheus-net.DotNetRuntime](https://github.com/djluck/prometheus-net.DotNetRuntime) from 4.2.0 to 4.2.1. - [Release notes](https://github.com/djluck/prometheus-net.DotNetRuntime/releases) - [Commits](https://github.com/djluck/prometheus-net.DotNetRuntime/compare/4.2.0...4.2.1) --- updated-dependencies: - dependency-name: prometheus-net.DotNetRuntime dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index fa24e9dd1db..ad4ad89d1f0 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -31,7 +31,7 @@ - + From fdec42bcccae21317c92c5e8a0a9cd46948f3944 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Sep 2021 12:00:58 +0000 Subject: [PATCH 042/278] Bump SQLitePCLRaw.bundle_e_sqlite3 from 2.0.5 to 2.0.6 Bumps [SQLitePCLRaw.bundle_e_sqlite3](https://github.com/ericsink/SQLitePCL.raw) from 2.0.5 to 2.0.6. - [Release notes](https://github.com/ericsink/SQLitePCL.raw/releases) - [Commits](https://github.com/ericsink/SQLitePCL.raw/compare/v2.0.5...v2.0.6) --- updated-dependencies: - dependency-name: SQLitePCLRaw.bundle_e_sqlite3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index a57666cd62f..1fdad73b74b 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -44,7 +44,7 @@ - + From c52fc714aa2c3386cd5f175e977afbc0c23eac9b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Sep 2021 12:01:04 +0000 Subject: [PATCH 043/278] Bump FsCheck.Xunit from 2.16.1 to 2.16.3 Bumps [FsCheck.Xunit](https://github.com/fsharp/FsCheck) from 2.16.1 to 2.16.3. - [Release notes](https://github.com/fsharp/FsCheck/releases) - [Changelog](https://github.com/fscheck/FsCheck/blob/master/FsCheck%20Release%20Notes.md) - [Commits](https://github.com/fsharp/FsCheck/compare/2.16.1...2.16.3) --- updated-dependencies: - dependency-name: FsCheck.Xunit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj | 2 +- .../Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj | 2 +- tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj | 2 +- .../Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index 8e6b07716c1..1619fa89c87 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj index 72cd9aa4503..20680157f52 100644 --- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj +++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj @@ -17,7 +17,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj index e9b7b185097..09b8a7a9486 100644 --- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj +++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj index dd593c9e740..5fa2ecfe9ed 100644 --- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj +++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj @@ -16,7 +16,7 @@ - + From 5f1b5cd30585345f5acec1444a47b67f8f5721fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Sep 2021 12:17:43 +0000 Subject: [PATCH 044/278] Bump Swashbuckle.AspNetCore.ReDoc from 6.1.5 to 6.2.1 Bumps [Swashbuckle.AspNetCore.ReDoc](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) from 6.1.5 to 6.2.1. - [Release notes](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases) - [Commits](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v6.1.5...v6.2.1) --- updated-dependencies: - dependency-name: Swashbuckle.AspNetCore.ReDoc dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 8f57879e1c5..2fca88f24ed 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -17,7 +17,7 @@ - + From 488e276f01b34c9aa82b53c969c56ccc9647cfbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Fern=C3=A1ndez?= Date: Mon, 6 Sep 2021 17:51:08 +0200 Subject: [PATCH 045/278] Fix Dockerfile typos --- Dockerfile | 4 ++++ Dockerfile.arm | 2 +- Dockerfile.arm64 | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 88bc8504f15..791a6113ede 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,7 @@ +# DESIGNED FOR BUILDING ON AMD64 ONLY +##################################### +# Requires binfm_misc registration +# https://github.com/multiarch/qemu-user-static#binfmt_misc-register ARG DOTNET_VERSION=5.0 FROM node:lts-alpine as web-builder diff --git a/Dockerfile.arm b/Dockerfile.arm index dcd006ff835..8d4b548bcd7 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -1,4 +1,4 @@ -# DESIGNED FOR BUILDING ON AMD64 ONLY +# DESIGNED FOR BUILDING ON ARM ONLY ##################################### # Requires binfm_misc registration # https://github.com/multiarch/qemu-user-static#binfmt_misc-register diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 7311c6b9ff2..835aa36a128 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -1,4 +1,4 @@ -# DESIGNED FOR BUILDING ON AMD64 ONLY +# DESIGNED FOR BUILDING ON ARM64 ONLY ##################################### # Requires binfm_misc registration # https://github.com/multiarch/qemu-user-static#binfmt_misc-register From 23e6c918a22c85751cc0907cd0074ee3f3445664 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Mon, 6 Sep 2021 11:08:40 -0600 Subject: [PATCH 046/278] Remove Linq to index search --- .../LiveTv/Listings/SchedulesDirect.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 6686b625f46..1f963e4a29f 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -10,7 +10,6 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; -using System.Net.Http.Json; using System.Net.Mime; using System.Text; using System.Text.Json; @@ -803,11 +802,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings { var channelNumber = GetChannelNumber(channel); - var station = allStations.FirstOrDefault(item => string.Equals(item.StationId, channel.StationId, StringComparison.OrdinalIgnoreCase)) - ?? new StationDto - { - StationId = channel.StationId - }; + var stationIndex = allStations.FindIndex(item => string.Equals(item.StationId, channel.StationId, StringComparison.OrdinalIgnoreCase)); + var station = stationIndex == -1 + ? new StationDto { StationId = channel.StationId } + : allStations[stationIndex]; var channelInfo = new ChannelInfo { From 59b67584fc18f73278404be33ddc8b7ea92019a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Fern=C3=A1ndez?= Date: Tue, 25 May 2021 16:47:29 +0200 Subject: [PATCH 047/278] Use appHost.Resolve for accessing the context in Program.cs --- Jellyfin.Server/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 6e87c8939f1..3c0ee069d4a 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -224,7 +224,7 @@ namespace Jellyfin.Server { _logger.LogInformation("Running query planner optimizations in the database... This might take a while"); // Run before disposing the application - using var context = new JellyfinDbProvider(appHost.ServiceProvider, appPaths).CreateContext(); + using var context = appHost.Resolve().CreateContext(); if (context.Database.IsSqlite()) { context.Database.ExecuteSqlRaw("PRAGMA optimize"); From 3d0b1ccae661704371041aadafc9816a223b1ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Fern=C3=A1ndez?= Date: Mon, 6 Sep 2021 21:15:21 +0200 Subject: [PATCH 048/278] Remove all unused usings --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 2 -- .../EntryPoints/ExternalPortForwarding.cs | 1 - Emby.Server.Implementations/IO/ManagedFileSystem.cs | 2 -- .../Library/Resolvers/TV/SeriesResolver.cs | 1 - Jellyfin.Api/Controllers/ApiKeyController.cs | 1 - Jellyfin.Api/Controllers/DynamicHlsController.cs | 1 - Jellyfin.Api/Controllers/ItemLookupController.cs | 4 ---- Jellyfin.Api/Controllers/LiveTvController.cs | 1 - Jellyfin.Api/Controllers/MoviesController.cs | 1 - Jellyfin.Api/Controllers/RemoteImageController.cs | 3 --- Jellyfin.Api/Controllers/SuggestionsController.cs | 1 - Jellyfin.Api/Controllers/VideoHlsController.cs | 3 --- Jellyfin.Api/Controllers/VideosController.cs | 2 -- Jellyfin.Api/Helpers/AudioHelper.cs | 2 -- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 2 -- Jellyfin.Api/Helpers/HlsHelpers.cs | 1 - Jellyfin.Api/Helpers/ProgressiveFileCopier.cs | 1 - Jellyfin.Api/Helpers/ProgressiveFileStream.cs | 1 - MediaBrowser.Controller/IServerApplicationHost.cs | 1 - MediaBrowser.Controller/Library/NameExtensions.cs | 1 - MediaBrowser.Controller/MediaEncoding/JobLogger.cs | 1 - MediaBrowser.Controller/Security/IAuthenticationManager.cs | 1 - MediaBrowser.Controller/Session/ISessionManager.cs | 2 -- MediaBrowser.Model/Dlna/DirectPlayProfile.cs | 1 - MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs | 1 - MediaBrowser.Model/Dlna/TranscodingProfile.cs | 1 - MediaBrowser.Providers/Manager/ItemImageProvider.cs | 1 - MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs | 1 - MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs | 1 - .../Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs | 1 - MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs | 1 - RSSDP/SsdpCommunicationsServer.cs | 1 - src/Jellyfin.Extensions/DictionaryExtensions.cs | 1 - .../Json/Converters/JsonCommaDelimitedArrayConverter.cs | 7 +------ .../Json/Converters/JsonPipeDelimitedArrayConverter.cs | 7 +------ .../Controllers/DynamicHlsControllerTests.cs | 7 ------- .../Json/Converters/JsonBoolNumberTests.cs | 1 - .../Probing/ProbeResultNormalizerTests.cs | 1 - tests/Jellyfin.Server.Tests/ParseNetworkTests.cs | 3 --- .../Parsers/MovieNfoParserTests.cs | 1 - 40 files changed, 2 insertions(+), 71 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 30f88c79649..88fc5018df9 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -16,7 +16,6 @@ using Emby.Server.Implementations.Playlists; using Jellyfin.Data.Enums; using Jellyfin.Extensions; using Jellyfin.Extensions.Json; -using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; @@ -25,7 +24,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 0a4efd73c7b..640754af40c 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -9,7 +9,6 @@ using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Data.Events; using Jellyfin.Networking.Configuration; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index f2f950ecbcb..1bc229b0cab 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -5,11 +5,9 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; -using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.IO diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index a1562abd312..4d8a6494c7a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -8,7 +8,6 @@ using System.IO; using Emby.Naming.TV; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; diff --git a/Jellyfin.Api/Controllers/ApiKeyController.cs b/Jellyfin.Api/Controllers/ApiKeyController.cs index 720b22b1d60..8e0332d3e16 100644 --- a/Jellyfin.Api/Controllers/ApiKeyController.cs +++ b/Jellyfin.Api/Controllers/ApiKeyController.cs @@ -1,4 +1,3 @@ -using System; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Jellyfin.Api.Constants; diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index fbfb4721549..a540033579b 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -5,7 +5,6 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs index 9fa307858fa..448510c06ab 100644 --- a/Jellyfin.Api/Controllers/ItemLookupController.cs +++ b/Jellyfin.Api/Controllers/ItemLookupController.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.IO; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; -using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -17,7 +14,6 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; using MediaBrowser.Model.Providers; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index b20eae7506d..93dc7672916 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Linq; using System.Net.Http; using System.Net.Mime; diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs index 010a3b19a7e..99c90d19e71 100644 --- a/Jellyfin.Api/Controllers/MoviesController.cs +++ b/Jellyfin.Api/Controllers/MoviesController.cs @@ -18,7 +18,6 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api.Controllers diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs index 8fec7d6df09..7a2c23991be 100644 --- a/Jellyfin.Api/Controllers/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/RemoteImageController.cs @@ -4,10 +4,8 @@ using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Net.Http; -using System.Net.Mime; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; @@ -16,7 +14,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; using MediaBrowser.Model.Providers; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs index a811a29c318..97eec4bd2ea 100644 --- a/Jellyfin.Api/Controllers/SuggestionsController.cs +++ b/Jellyfin.Api/Controllers/SuggestionsController.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel.DataAnnotations; -using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.ModelBinders; diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index 8560df2eac1..9c5e968dd8d 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; using System.IO; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Attributes; @@ -20,12 +19,10 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.Controllers diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 5c5057c83b6..bc6fc904a12 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -25,14 +25,12 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; namespace Jellyfin.Api.Controllers { diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs index 264131905fd..ddcde1cf6dd 100644 --- a/Jellyfin.Api/Helpers/AudioHelper.cs +++ b/Jellyfin.Api/Helpers/AudioHelper.cs @@ -11,12 +11,10 @@ using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; -using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; namespace Jellyfin.Api.Helpers { diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index dc5d6715b53..4abe4c5d572 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -18,11 +18,9 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; diff --git a/Jellyfin.Api/Helpers/HlsHelpers.cs b/Jellyfin.Api/Helpers/HlsHelpers.cs index 0c226f4297d..f36769dc2a2 100644 --- a/Jellyfin.Api/Helpers/HlsHelpers.cs +++ b/Jellyfin.Api/Helpers/HlsHelpers.cs @@ -1,7 +1,6 @@ using System; using System.Globalization; using System.IO; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Models.StreamingDtos; diff --git a/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs b/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs index 1fb4798eed9..81970b041a7 100644 --- a/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs +++ b/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs @@ -1,7 +1,6 @@ using System; using System.Buffers; using System.IO; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Models.PlaybackDtos; diff --git a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs index 82f35fc3581..d4cc0172d9e 100644 --- a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs +++ b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs @@ -1,7 +1,6 @@ using System; using System.Diagnostics; using System.IO; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Models.PlaybackDtos; diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index b0abca367cb..3da0a587530 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -2,7 +2,6 @@ #pragma warning disable CS1591 -using System; using System.Collections.Generic; using System.Net; using MediaBrowser.Common; diff --git a/MediaBrowser.Controller/Library/NameExtensions.cs b/MediaBrowser.Controller/Library/NameExtensions.cs index a49dcacc1dc..d2ed3465a81 100644 --- a/MediaBrowser.Controller/Library/NameExtensions.cs +++ b/MediaBrowser.Controller/Library/NameExtensions.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; using Diacritics.Extensions; -using MediaBrowser.Controller.Extensions; namespace MediaBrowser.Controller.Library { diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs index b23c9511271..aa5e2c4038e 100644 --- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs +++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs @@ -6,7 +6,6 @@ using System; using System.Globalization; using System.IO; using System.Text; -using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; diff --git a/MediaBrowser.Controller/Security/IAuthenticationManager.cs b/MediaBrowser.Controller/Security/IAuthenticationManager.cs index 29621b73e7f..e3d18c8c09d 100644 --- a/MediaBrowser.Controller/Security/IAuthenticationManager.cs +++ b/MediaBrowser.Controller/Security/IAuthenticationManager.cs @@ -1,6 +1,5 @@ #nullable enable -using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index cc12cb102b1..1f34d2bf185 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -10,8 +10,6 @@ using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Events; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Security; -using MediaBrowser.Model.Devices; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs index fa3ad098f01..03c3a726574 100644 --- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 -using System.ComponentModel.DataAnnotations; using System.Xml.Serialization; namespace MediaBrowser.Model.Dlna diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs index 7ce248509c7..93a9ae61598 100644 --- a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs +++ b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Model.Dlna diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index 214578a85e6..709bdad3126 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -1,7 +1,6 @@ #pragma warning disable CS1591 using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.Xml.Serialization; namespace MediaBrowser.Model.Dlna diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index a3002fc8dd5..4b325f2cf3a 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Net; diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs index aa0743bd028..449f0d25915 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index b3d06592908..d7f6a5fac24 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.IO; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index c97affdbf25..93f8902def4 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -12,7 +12,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; -using MediaBrowser.Common; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs index ca3ec79b787..3a305024ee4 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs @@ -1,7 +1,6 @@ using System; using System.Globalization; using System.IO; -using System.Text; using System.Threading; using System.Xml; using MediaBrowser.Common.Configuration; diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index e49c0e77bef..e0116c06863 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Http; diff --git a/src/Jellyfin.Extensions/DictionaryExtensions.cs b/src/Jellyfin.Extensions/DictionaryExtensions.cs index 43ed41ab18a..5bb828d016a 100644 --- a/src/Jellyfin.Extensions/DictionaryExtensions.cs +++ b/src/Jellyfin.Extensions/DictionaryExtensions.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; namespace Jellyfin.Extensions diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedArrayConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedArrayConverter.cs index 44980ec02f2..0d0cc2d0639 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedArrayConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedArrayConverter.cs @@ -1,9 +1,4 @@ -using System; -using System.ComponentModel; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Jellyfin.Extensions.Json.Converters +namespace Jellyfin.Extensions.Json.Converters { /// /// Convert comma delimited string to array of type. diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedArrayConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedArrayConverter.cs index e3e492e24d8..6e59fe46414 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedArrayConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedArrayConverter.cs @@ -1,9 +1,4 @@ -using System; -using System.ComponentModel; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Jellyfin.Extensions.Json.Converters +namespace Jellyfin.Extensions.Json.Converters { /// /// Convert Pipe delimited string to array of type. diff --git a/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs index 11708381569..59a6b52d159 100644 --- a/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs +++ b/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs @@ -1,13 +1,6 @@ using System; using System.Collections.Generic; -using AutoFixture; -using AutoFixture.AutoMoq; using Jellyfin.Api.Controllers; -using Jellyfin.Api.Helpers; -using Jellyfin.Api.Models.StreamingDtos; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using Moq; using Xunit; namespace Jellyfin.Api.Tests.Controllers diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolNumberTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolNumberTests.cs index d0e3e94568a..125229ff92e 100644 --- a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolNumberTests.cs +++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolNumberTests.cs @@ -1,4 +1,3 @@ -using System.Globalization; using System.Text.Json; using FsCheck; using FsCheck.Xunit; diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index fcb85a3acfb..d002d5a3475 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -4,7 +4,6 @@ using System.IO; using System.Text.Json; using Jellyfin.Extensions.Json; using MediaBrowser.MediaEncoding.Probing; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging.Abstractions; diff --git a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs index b92cb165c98..a1bdfa31b8a 100644 --- a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs +++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Net; -using System.Text; using Jellyfin.Networking.Configuration; using Jellyfin.Networking.Manager; using Jellyfin.Server.Extensions; diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs index ef3ca15d5af..7ea45d14d08 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs @@ -11,7 +11,6 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.System; using MediaBrowser.Providers.Plugins.Tmdb.Movies; using MediaBrowser.XbmcMetadata.Parsers; using Microsoft.Extensions.Logging.Abstractions; From c2652d21e1da0c39e91b8a5dea21c4de1fea3f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Fern=C3=A1ndez?= Date: Mon, 6 Sep 2021 20:41:37 +0200 Subject: [PATCH 049/278] Log EFCore migrations --- .../JellyfinDbProvider.cs | 14 ++++++++++++-- Jellyfin.Server/CoreAppHost.cs | 3 ++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs index 486be605374..c2c5198d14f 100644 --- a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs +++ b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs @@ -1,8 +1,10 @@ using System; using System.IO; +using System.Linq; using MediaBrowser.Common.Configuration; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Implementations { @@ -13,19 +15,27 @@ namespace Jellyfin.Server.Implementations { private readonly IServiceProvider _serviceProvider; private readonly IApplicationPaths _appPaths; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// The application's service provider. /// The application paths. - public JellyfinDbProvider(IServiceProvider serviceProvider, IApplicationPaths appPaths) + /// The logger. + public JellyfinDbProvider(IServiceProvider serviceProvider, IApplicationPaths appPaths, ILogger logger) { _serviceProvider = serviceProvider; _appPaths = appPaths; + _logger = logger; using var jellyfinDb = CreateContext(); - jellyfinDb.Database.Migrate(); + if (jellyfinDb.Database.GetPendingMigrations().Any()) + { + _logger.LogInformation("There are pending EFCore migrations in the database. Applying... (This may take a while, do not stop Jellyfin)"); + jellyfinDb.Database.Migrate(); + _logger.LogInformation("EFCore migrations applied successfully"); + } } /// diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index d41b5f74ec8..60275c9189c 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -78,7 +78,8 @@ namespace Jellyfin.Server } ServiceCollection.AddDbContextPool( - options => options.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}")); + options => options.UseLoggerFactory(LoggerFactory). + UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}")); ServiceCollection.AddEventServices(); ServiceCollection.AddSingleton(); From f4af78817d5832c11ff96f365714917fad0db4f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Fern=C3=A1ndez?= Date: Mon, 6 Sep 2021 21:05:21 +0200 Subject: [PATCH 050/278] Move model configuration to its own classes --- Jellyfin.Server.Implementations/JellyfinDb.cs | 94 +------------------ .../ModelConfiguration/ApiKeyConfiguration.cs | 22 +++++ ...stomItemDisplayPreferencesConfiguration.cs | 22 +++++ .../ModelConfiguration/DeviceConfiguration.cs | 30 ++++++ .../DeviceOptionsConfiguration.cs | 22 +++++ .../DisplayPreferencesConfiguration.cs | 29 ++++++ .../PermissionConfiguration.cs | 26 +++++ .../PreferenceConfiguration.cs | 23 +++++ .../ModelConfiguration/UserConfiguration.cs | 62 ++++++++++++ 9 files changed, 238 insertions(+), 92 deletions(-) create mode 100644 Jellyfin.Server.Implementations/ModelConfiguration/ApiKeyConfiguration.cs create mode 100644 Jellyfin.Server.Implementations/ModelConfiguration/CustomItemDisplayPreferencesConfiguration.cs create mode 100644 Jellyfin.Server.Implementations/ModelConfiguration/DeviceConfiguration.cs create mode 100644 Jellyfin.Server.Implementations/ModelConfiguration/DeviceOptionsConfiguration.cs create mode 100644 Jellyfin.Server.Implementations/ModelConfiguration/DisplayPreferencesConfiguration.cs create mode 100644 Jellyfin.Server.Implementations/ModelConfiguration/PermissionConfiguration.cs create mode 100644 Jellyfin.Server.Implementations/ModelConfiguration/PreferenceConfiguration.cs create mode 100644 Jellyfin.Server.Implementations/ModelConfiguration/UserConfiguration.cs diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 6f35a2c1c2b..b176e14a619 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -153,100 +153,10 @@ namespace Jellyfin.Server.Implementations { modelBuilder.SetDefaultDateTimeKind(DateTimeKind.Utc); base.OnModelCreating(modelBuilder); - modelBuilder.HasDefaultSchema("jellyfin"); - // Collations - - modelBuilder.Entity() - .Property(user => user.Username) - .UseCollation("NOCASE"); - - // Delete behavior - - modelBuilder.Entity() - .HasOne(u => u.ProfileImage) - .WithOne() - .OnDelete(DeleteBehavior.Cascade); - - modelBuilder.Entity() - .HasMany(u => u.Permissions) - .WithOne() - .HasForeignKey(p => p.UserId) - .OnDelete(DeleteBehavior.Cascade); - - modelBuilder.Entity() - .HasMany(u => u.Preferences) - .WithOne() - .HasForeignKey(p => p.UserId) - .OnDelete(DeleteBehavior.Cascade); - - modelBuilder.Entity() - .HasMany(u => u.AccessSchedules) - .WithOne() - .OnDelete(DeleteBehavior.Cascade); - - modelBuilder.Entity() - .HasMany(u => u.DisplayPreferences) - .WithOne() - .OnDelete(DeleteBehavior.Cascade); - - modelBuilder.Entity() - .HasMany(u => u.ItemDisplayPreferences) - .WithOne() - .OnDelete(DeleteBehavior.Cascade); - - modelBuilder.Entity() - .HasMany(d => d.HomeSections) - .WithOne() - .OnDelete(DeleteBehavior.Cascade); - - // Indexes - - modelBuilder.Entity() - .HasIndex(entity => entity.AccessToken) - .IsUnique(); - - modelBuilder.Entity() - .HasIndex(entity => entity.Username) - .IsUnique(); - - modelBuilder.Entity() - .HasIndex(entity => new { entity.DeviceId, entity.DateLastActivity }); - - modelBuilder.Entity() - .HasIndex(entity => new { entity.AccessToken, entity.DateLastActivity }); - - modelBuilder.Entity() - .HasIndex(entity => new { entity.UserId, entity.DeviceId }); - - modelBuilder.Entity() - .HasIndex(entity => entity.DeviceId); - - modelBuilder.Entity() - .HasIndex(entity => entity.DeviceId) - .IsUnique(); - - modelBuilder.Entity() - .HasIndex(entity => new { entity.UserId, entity.ItemId, entity.Client }) - .IsUnique(); - - modelBuilder.Entity() - .HasIndex(entity => new { entity.UserId, entity.ItemId, entity.Client, entity.Key }) - .IsUnique(); - - // Used to get a user's permissions or a specific permission for a user. - // Also prevents multiple values being created for a user. - // Filtered over non-null user ids for when other entities (groups, API keys) get permissions - modelBuilder.Entity() - .HasIndex(p => new { p.UserId, p.Kind }) - .HasFilter("[UserId] IS NOT NULL") - .IsUnique(); - - modelBuilder.Entity() - .HasIndex(p => new { p.UserId, p.Kind }) - .HasFilter("[UserId] IS NOT NULL") - .IsUnique(); + // Configuration for each entity is in it's own class inside 'ModelConfiguratio'. + modelBuilder.ApplyConfigurationsFromAssembly(typeof(JellyfinDb).Assembly); } } } diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/ApiKeyConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/ApiKeyConfiguration.cs new file mode 100644 index 00000000000..9067d3833dd --- /dev/null +++ b/Jellyfin.Server.Implementations/ModelConfiguration/ApiKeyConfiguration.cs @@ -0,0 +1,22 @@ +using Jellyfin.Data.Entities.Security; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Jellyfin.Server.Implementations.ModelConfiguration +{ + /// + /// FluentAPI configuration for the ApiKey entity. + /// + public class ApiKeyConfiguration : IEntityTypeConfiguration + { + /// + public void Configure(EntityTypeBuilder builder) + { + // Indexes + + builder + .HasIndex(entity => entity.AccessToken) + .IsUnique(); + } + } +} diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/CustomItemDisplayPreferencesConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/CustomItemDisplayPreferencesConfiguration.cs new file mode 100644 index 00000000000..75d9ba529e9 --- /dev/null +++ b/Jellyfin.Server.Implementations/ModelConfiguration/CustomItemDisplayPreferencesConfiguration.cs @@ -0,0 +1,22 @@ +using Jellyfin.Data.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Jellyfin.Server.Implementations.ModelConfiguration +{ + /// + /// FluentAPI configuration for the CustomItemDisplayPreferences entity. + /// + public class CustomItemDisplayPreferencesConfiguration : IEntityTypeConfiguration + { + /// + public void Configure(EntityTypeBuilder builder) + { + // Indexes + + builder + .HasIndex(entity => new { entity.UserId, entity.ItemId, entity.Client, entity.Key }) + .IsUnique(); + } + } +} diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/DeviceConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/DeviceConfiguration.cs new file mode 100644 index 00000000000..5b4e711cc4d --- /dev/null +++ b/Jellyfin.Server.Implementations/ModelConfiguration/DeviceConfiguration.cs @@ -0,0 +1,30 @@ +using Jellyfin.Data.Entities.Security; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Jellyfin.Server.Implementations.ModelConfiguration +{ + /// + /// FluentAPI configuration for the Device entity. + /// + public class DeviceConfiguration : IEntityTypeConfiguration + { + /// + public void Configure(EntityTypeBuilder builder) + { + // Indexes + + builder + .HasIndex(entity => new { entity.DeviceId, entity.DateLastActivity }); + + builder + .HasIndex(entity => new { entity.AccessToken, entity.DateLastActivity }); + + builder + .HasIndex(entity => new { entity.UserId, entity.DeviceId }); + + builder + .HasIndex(entity => entity.DeviceId); + } + } +} diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/DeviceOptionsConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/DeviceOptionsConfiguration.cs new file mode 100644 index 00000000000..0db1944b58b --- /dev/null +++ b/Jellyfin.Server.Implementations/ModelConfiguration/DeviceOptionsConfiguration.cs @@ -0,0 +1,22 @@ +using Jellyfin.Data.Entities.Security; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Jellyfin.Server.Implementations.ModelConfiguration +{ + /// + /// FluentAPI configuration for the DeviceOptions entity. + /// + public class DeviceOptionsConfiguration : IEntityTypeConfiguration + { + /// + public void Configure(EntityTypeBuilder builder) + { + // Indexes + + builder + .HasIndex(entity => entity.DeviceId) + .IsUnique(); + } + } +} diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/DisplayPreferencesConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/DisplayPreferencesConfiguration.cs new file mode 100644 index 00000000000..807078803d2 --- /dev/null +++ b/Jellyfin.Server.Implementations/ModelConfiguration/DisplayPreferencesConfiguration.cs @@ -0,0 +1,29 @@ +using Jellyfin.Data.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Jellyfin.Server.Implementations.ModelConfiguration +{ + /// + /// FluentAPI configuration for the DisplayPreferencesConfiguration entity. + /// + public class DisplayPreferencesConfiguration : IEntityTypeConfiguration + { + /// + public void Configure(EntityTypeBuilder builder) + { + // Delete behaviour + + builder + .HasMany(d => d.HomeSections) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + // Indexes + + builder + .HasIndex(entity => new { entity.UserId, entity.ItemId, entity.Client }) + .IsUnique(); + } + } +} diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/PermissionConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/PermissionConfiguration.cs new file mode 100644 index 00000000000..56e76d88dd3 --- /dev/null +++ b/Jellyfin.Server.Implementations/ModelConfiguration/PermissionConfiguration.cs @@ -0,0 +1,26 @@ +using Jellyfin.Data.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Jellyfin.Server.Implementations.ModelConfiguration +{ + /// + /// FluentAPI configuration for the Permission entity. + /// + public class PermissionConfiguration : IEntityTypeConfiguration + { + /// + public void Configure(EntityTypeBuilder builder) + { + // Indexes + + // Used to get a user's permissions or a specific permission for a user. + // Also prevents multiple values being created for a user. + // Filtered over non-null user ids for when other entities (groups, API keys) get permissions + builder + .HasIndex(p => new { p.UserId, p.Kind }) + .HasFilter("[UserId] IS NOT NULL") + .IsUnique(); + } + } +} diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/PreferenceConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/PreferenceConfiguration.cs new file mode 100644 index 00000000000..3cd5f8afbee --- /dev/null +++ b/Jellyfin.Server.Implementations/ModelConfiguration/PreferenceConfiguration.cs @@ -0,0 +1,23 @@ +using Jellyfin.Data.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Jellyfin.Server.Implementations.ModelConfiguration +{ + /// + /// FluentAPI configuration for the Permission entity. + /// + public class PreferenceConfiguration : IEntityTypeConfiguration + { + /// + public void Configure(EntityTypeBuilder builder) + { + // Indexes + + builder + .HasIndex(p => new { p.UserId, p.Kind }) + .HasFilter("[UserId] IS NOT NULL") + .IsUnique(); + } + } +} diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/UserConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/UserConfiguration.cs new file mode 100644 index 00000000000..ee883180489 --- /dev/null +++ b/Jellyfin.Server.Implementations/ModelConfiguration/UserConfiguration.cs @@ -0,0 +1,62 @@ +using Jellyfin.Data.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Jellyfin.Server.Implementations.ModelConfiguration +{ + /// + /// FluentAPI configuration for the User entity. + /// + public class UserConfiguration : IEntityTypeConfiguration + { + /// + public void Configure(EntityTypeBuilder builder) + { + // Collations + + builder + .Property(user => user.Username) + .UseCollation("NOCASE"); + + // Delete behavior + + builder + .HasOne(u => u.ProfileImage) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + builder + .HasMany(u => u.Permissions) + .WithOne() + .HasForeignKey(p => p.UserId) + .OnDelete(DeleteBehavior.Cascade); + + builder + .HasMany(u => u.Preferences) + .WithOne() + .HasForeignKey(p => p.UserId) + .OnDelete(DeleteBehavior.Cascade); + + builder + .HasMany(u => u.AccessSchedules) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + builder + .HasMany(u => u.DisplayPreferences) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + builder + .HasMany(u => u.ItemDisplayPreferences) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + // Indexes + + builder + .HasIndex(entity => entity.Username) + .IsUnique(); + } + } +} From 154b7d8505e27dca9b5f68b2cf69d8c7dd44dcc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Fern=C3=A1ndez?= Date: Mon, 6 Sep 2021 21:35:54 +0200 Subject: [PATCH 051/278] Fix identation --- Jellyfin.Server/CoreAppHost.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 60275c9189c..21bd9ba0114 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -78,8 +78,9 @@ namespace Jellyfin.Server } ServiceCollection.AddDbContextPool( - options => options.UseLoggerFactory(LoggerFactory). - UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}")); + options => options + .UseLoggerFactory(LoggerFactory) + .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}")); ServiceCollection.AddEventServices(); ServiceCollection.AddSingleton(); From fdab8eebc906f18b75dd85a0f3416d74a98a24fe Mon Sep 17 00:00:00 2001 From: cvium Date: Tue, 7 Sep 2021 08:54:58 +0200 Subject: [PATCH 052/278] Fix disposed exception when ffmpeg exits early in GetLiveHlsStream --- Jellyfin.Api/Controllers/VideoHlsController.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index 8560df2eac1..8810a12fba6 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -267,6 +267,9 @@ namespace Jellyfin.Api.Controllers // CTS lifecycle is managed internally. var cancellationTokenSource = new CancellationTokenSource(); + // Due to CTS.Token calling ThrowIfDisposed (https://github.com/dotnet/runtime/issues/29970) we have to "cache" the token + // since it gets disposed when ffmpeg exits + var cancellationToken = cancellationTokenSource.Token; using var state = await StreamingHelpers.GetStreamingState( streamingRequest, Request, @@ -281,7 +284,7 @@ namespace Jellyfin.Api.Controllers _deviceManager, _transcodingJobHelper, TranscodingJobType, - cancellationTokenSource.Token) + cancellationToken) .ConfigureAwait(false); TranscodingJobDto? job = null; @@ -290,7 +293,7 @@ namespace Jellyfin.Api.Controllers if (!System.IO.File.Exists(playlistPath)) { var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath); - await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); + await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false); try { if (!System.IO.File.Exists(playlistPath)) @@ -317,7 +320,7 @@ namespace Jellyfin.Api.Controllers minSegments = state.MinSegments; if (minSegments > 0) { - await HlsHelpers.WaitForMinimumSegmentCount(playlistPath, minSegments, _logger, cancellationTokenSource.Token).ConfigureAwait(false); + await HlsHelpers.WaitForMinimumSegmentCount(playlistPath, minSegments, _logger, cancellationToken).ConfigureAwait(false); } } } From 8dc0911374a784b945e7b58a0c9209d6a2a6d374 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 7 Sep 2021 14:03:32 +0200 Subject: [PATCH 053/278] Fix log message --- Emby.Server.Implementations/Library/MediaSourceManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 4e0434b92a8..523e6c93c65 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -521,7 +521,7 @@ namespace Emby.Server.Implementations.Library // TODO: @bond Fix var json = JsonSerializer.SerializeToUtf8Bytes(mediaSource, _jsonOptions); - _logger.LogInformation("Live stream opened: " + json); + _logger.LogInformation("Live stream opened: {@Json}", json); var clone = JsonSerializer.Deserialize(json, _jsonOptions); if (!request.UserId.Equals(Guid.Empty)) From 71ab4a5754ff2c8e7477ebd6a2b948bb973da7a2 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 7 Sep 2021 14:18:04 +0200 Subject: [PATCH 054/278] Fix it for real(tm) this time --- Emby.Server.Implementations/Library/MediaSourceManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 523e6c93c65..6f83973ba19 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -521,7 +521,7 @@ namespace Emby.Server.Implementations.Library // TODO: @bond Fix var json = JsonSerializer.SerializeToUtf8Bytes(mediaSource, _jsonOptions); - _logger.LogInformation("Live stream opened: {@Json}", json); + _logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource); var clone = JsonSerializer.Deserialize(json, _jsonOptions); if (!request.UserId.Equals(Guid.Empty)) From e5980f868643e4e92448bbfb31a0fedae3929691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Fern=C3=A1ndez?= Date: Tue, 7 Sep 2021 17:53:35 +0200 Subject: [PATCH 055/278] Fix typo in comment and remove useless ones --- Jellyfin.Server.Implementations/JellyfinDb.cs | 2 +- .../ModelConfiguration/ApiKeyConfiguration.cs | 2 -- .../CustomItemDisplayPreferencesConfiguration.cs | 2 -- .../ModelConfiguration/DeviceConfiguration.cs | 2 -- .../ModelConfiguration/DeviceOptionsConfiguration.cs | 2 -- .../ModelConfiguration/DisplayPreferencesConfiguration.cs | 4 ---- .../ModelConfiguration/PermissionConfiguration.cs | 2 -- .../ModelConfiguration/PreferenceConfiguration.cs | 2 -- .../ModelConfiguration/UserConfiguration.cs | 6 ------ 9 files changed, 1 insertion(+), 23 deletions(-) diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index b176e14a619..dc4f53913c0 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -155,7 +155,7 @@ namespace Jellyfin.Server.Implementations base.OnModelCreating(modelBuilder); modelBuilder.HasDefaultSchema("jellyfin"); - // Configuration for each entity is in it's own class inside 'ModelConfiguratio'. + // Configuration for each entity is in it's own class inside 'ModelConfiguration'. modelBuilder.ApplyConfigurationsFromAssembly(typeof(JellyfinDb).Assembly); } } diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/ApiKeyConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/ApiKeyConfiguration.cs index 9067d3833dd..3f19b6986a8 100644 --- a/Jellyfin.Server.Implementations/ModelConfiguration/ApiKeyConfiguration.cs +++ b/Jellyfin.Server.Implementations/ModelConfiguration/ApiKeyConfiguration.cs @@ -12,8 +12,6 @@ namespace Jellyfin.Server.Implementations.ModelConfiguration /// public void Configure(EntityTypeBuilder builder) { - // Indexes - builder .HasIndex(entity => entity.AccessToken) .IsUnique(); diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/CustomItemDisplayPreferencesConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/CustomItemDisplayPreferencesConfiguration.cs index 75d9ba529e9..779aec986bb 100644 --- a/Jellyfin.Server.Implementations/ModelConfiguration/CustomItemDisplayPreferencesConfiguration.cs +++ b/Jellyfin.Server.Implementations/ModelConfiguration/CustomItemDisplayPreferencesConfiguration.cs @@ -12,8 +12,6 @@ namespace Jellyfin.Server.Implementations.ModelConfiguration /// public void Configure(EntityTypeBuilder builder) { - // Indexes - builder .HasIndex(entity => new { entity.UserId, entity.ItemId, entity.Client, entity.Key }) .IsUnique(); diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/DeviceConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/DeviceConfiguration.cs index 5b4e711cc4d..a750b65c036 100644 --- a/Jellyfin.Server.Implementations/ModelConfiguration/DeviceConfiguration.cs +++ b/Jellyfin.Server.Implementations/ModelConfiguration/DeviceConfiguration.cs @@ -12,8 +12,6 @@ namespace Jellyfin.Server.Implementations.ModelConfiguration /// public void Configure(EntityTypeBuilder builder) { - // Indexes - builder .HasIndex(entity => new { entity.DeviceId, entity.DateLastActivity }); diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/DeviceOptionsConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/DeviceOptionsConfiguration.cs index 0db1944b58b..038afd75247 100644 --- a/Jellyfin.Server.Implementations/ModelConfiguration/DeviceOptionsConfiguration.cs +++ b/Jellyfin.Server.Implementations/ModelConfiguration/DeviceOptionsConfiguration.cs @@ -12,8 +12,6 @@ namespace Jellyfin.Server.Implementations.ModelConfiguration /// public void Configure(EntityTypeBuilder builder) { - // Indexes - builder .HasIndex(entity => entity.DeviceId) .IsUnique(); diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/DisplayPreferencesConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/DisplayPreferencesConfiguration.cs index 807078803d2..9b437861bb8 100644 --- a/Jellyfin.Server.Implementations/ModelConfiguration/DisplayPreferencesConfiguration.cs +++ b/Jellyfin.Server.Implementations/ModelConfiguration/DisplayPreferencesConfiguration.cs @@ -12,15 +12,11 @@ namespace Jellyfin.Server.Implementations.ModelConfiguration /// public void Configure(EntityTypeBuilder builder) { - // Delete behaviour - builder .HasMany(d => d.HomeSections) .WithOne() .OnDelete(DeleteBehavior.Cascade); - // Indexes - builder .HasIndex(entity => new { entity.UserId, entity.ItemId, entity.Client }) .IsUnique(); diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/PermissionConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/PermissionConfiguration.cs index 56e76d88dd3..240e284c0c2 100644 --- a/Jellyfin.Server.Implementations/ModelConfiguration/PermissionConfiguration.cs +++ b/Jellyfin.Server.Implementations/ModelConfiguration/PermissionConfiguration.cs @@ -12,8 +12,6 @@ namespace Jellyfin.Server.Implementations.ModelConfiguration /// public void Configure(EntityTypeBuilder builder) { - // Indexes - // Used to get a user's permissions or a specific permission for a user. // Also prevents multiple values being created for a user. // Filtered over non-null user ids for when other entities (groups, API keys) get permissions diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/PreferenceConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/PreferenceConfiguration.cs index 3cd5f8afbee..49c869c6a8d 100644 --- a/Jellyfin.Server.Implementations/ModelConfiguration/PreferenceConfiguration.cs +++ b/Jellyfin.Server.Implementations/ModelConfiguration/PreferenceConfiguration.cs @@ -12,8 +12,6 @@ namespace Jellyfin.Server.Implementations.ModelConfiguration /// public void Configure(EntityTypeBuilder builder) { - // Indexes - builder .HasIndex(p => new { p.UserId, p.Kind }) .HasFilter("[UserId] IS NOT NULL") diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/UserConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/UserConfiguration.cs index ee883180489..a369cf65629 100644 --- a/Jellyfin.Server.Implementations/ModelConfiguration/UserConfiguration.cs +++ b/Jellyfin.Server.Implementations/ModelConfiguration/UserConfiguration.cs @@ -12,14 +12,10 @@ namespace Jellyfin.Server.Implementations.ModelConfiguration /// public void Configure(EntityTypeBuilder builder) { - // Collations - builder .Property(user => user.Username) .UseCollation("NOCASE"); - // Delete behavior - builder .HasOne(u => u.ProfileImage) .WithOne() @@ -52,8 +48,6 @@ namespace Jellyfin.Server.Implementations.ModelConfiguration .WithOne() .OnDelete(DeleteBehavior.Cascade); - // Indexes - builder .HasIndex(entity => entity.Username) .IsUnique(); From 95f287e8199713b63f8e3f46cc6bd208979fee74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Tue, 7 Sep 2021 16:31:58 +0000 Subject: [PATCH 056/278] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cs/ --- Emby.Server.Implementations/Localization/Core/cs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index 4f1d231a4a3..1ea378321c0 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -25,7 +25,7 @@ "HeaderLiveTV": "Televize", "HeaderNextUp": "Nadcházející", "HeaderRecordingGroups": "Skupiny nahrávek", - "HomeVideos": "Domáci videa", + "HomeVideos": "Domácí videa", "Inherit": "Zdědit", "ItemAddedWithName": "{0} byl přidán do knihovny", "ItemRemovedWithName": "{0} byl odstraněn z knihovny", From 1a5d8e89da713b589855a1e0edcfcde0879a1f49 Mon Sep 17 00:00:00 2001 From: hoanghuy309 Date: Tue, 7 Sep 2021 09:00:40 +0000 Subject: [PATCH 057/278] Translated using Weblate (Vietnamese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/ --- Emby.Server.Implementations/Localization/Core/vi.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json index 3d69e418b99..6d073b3871e 100644 --- a/Emby.Server.Implementations/Localization/Core/vi.json +++ b/Emby.Server.Implementations/Localization/Core/vi.json @@ -10,11 +10,11 @@ "Photos": "Ảnh", "Playlists": "Danh sách phát", "Shows": "Chương Trình TV", - "Songs": "Các Bài Hát", + "Songs": "Bài Hát", "Sync": "Đồng Bộ", "ValueSpecialEpisodeName": "Đặc Biệt - {0}", "Albums": "Tuyển Tập", - "Artists": "Các Nghệ Sĩ", + "Artists": "Ca Sĩ", "TaskDownloadMissingSubtitlesDescription": "Tìm kiếm phụ đề bị thiếu trên Internet dựa trên cấu hình dữ liệu mô tả.", "TaskDownloadMissingSubtitles": "Tải Xuống Phụ Đề Bị Thiếu", "TaskRefreshChannelsDescription": "Làm mới thông tin kênh internet.", From 0d16c489985da50ddd13ef228c73b3bcb0ae5f67 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 9 Sep 2021 15:59:13 +0200 Subject: [PATCH 058/278] Fix some warnings --- MediaBrowser.Controller/Entities/Folder.cs | 6 +++--- .../Entities/UserRootFolder.cs | 16 ++++++++-------- .../MediaEncoding/EncodingHelper.cs | 12 ++++++------ .../RemoveFromPlaylistGroupRequest.cs | 1 - .../Configuration/MetadataOptions.cs | 2 +- MediaBrowser.Model/Dlna/ResolutionNormalizer.cs | 2 +- MediaBrowser.Model/Users/UserPolicy.cs | 2 +- .../MediaBrowser.Providers.csproj | 2 -- jellyfin.ruleset | 10 +++++++--- 9 files changed, 27 insertions(+), 26 deletions(-) diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index dd08c31eda2..18b4ec3c663 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1015,17 +1015,17 @@ namespace MediaBrowser.Controller.Entities if (!string.IsNullOrEmpty(query.NameStartsWithOrGreater)) { - items = items.Where(i => string.Compare(query.NameStartsWithOrGreater, i.SortName, StringComparison.CurrentCultureIgnoreCase) < 1); + items = items.Where(i => string.Compare(query.NameStartsWithOrGreater, i.SortName, StringComparison.InvariantCultureIgnoreCase) < 1); } if (!string.IsNullOrEmpty(query.NameStartsWith)) { - items = items.Where(i => i.SortName.StartsWith(query.NameStartsWith, StringComparison.CurrentCultureIgnoreCase)); + items = items.Where(i => i.SortName.StartsWith(query.NameStartsWith, StringComparison.InvariantCultureIgnoreCase)); } if (!string.IsNullOrEmpty(query.NameLessThan)) { - items = items.Where(i => string.Compare(query.NameLessThan, i.SortName, StringComparison.CurrentCultureIgnoreCase) == 1); + items = items.Where(i => string.Compare(query.NameLessThan, i.SortName, StringComparison.InvariantCultureIgnoreCase) == 1); } // This must be the last filter diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index c07fb40b3e8..e547db52315 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -24,6 +24,14 @@ namespace MediaBrowser.Controller.Entities private readonly object _childIdsLock = new object(); private List _childrenIds = null; + /// + /// Initializes a new instance of the class. + /// + public UserRootFolder() + { + IsRoot = true; + } + [JsonIgnore] public override bool SupportsInheritedParentImages => false; @@ -44,14 +52,6 @@ namespace MediaBrowser.Controller.Entities } } - /// - /// Initializes a new instance of the class. - /// - public UserRootFolder() - { - IsRoot = true; - } - protected override List LoadChildren() { lock (_childIdsLock) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index ec44150a2e3..bdb3793325a 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Controller.MediaEncoding "ConstrainedHigh" }; - private static readonly Version minVersionForCudaOverlay = new Version(4, 4); + private static readonly Version _minVersionForCudaOverlay = new Version(4, 4); public EncodingHelper( IMediaEncoder mediaEncoder, @@ -647,8 +647,8 @@ namespace MediaBrowser.Controller.MediaEncoding } if (state.IsVideoRequest - && ((string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) - && (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder)))) + && string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) + && (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder)) { if (!isCudaTonemappingSupported && isOpenclTonemappingSupported) { @@ -2099,7 +2099,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion(); - var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay; + var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= _minVersionForCudaOverlay; var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat); // Tonemapping and burn-in graphical subtitles requires overlay_vaapi. @@ -2380,7 +2380,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options); var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase); var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion(); - var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay; + var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= _minVersionForCudaOverlay; var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat); var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; @@ -2683,7 +2683,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options); var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion(); - var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay; + var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= _minVersionForCudaOverlay; var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs index 856f175df38..2f38d6adc33 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs @@ -19,7 +19,6 @@ namespace MediaBrowser.Controller.SyncPlay.PlaybackRequests /// The playlist ids of the items to remove. /// Whether to clear the entire playlist. The items list will be ignored. /// Whether to remove the playing item as well. Used only when clearing the playlist. - public RemoveFromPlaylistGroupRequest(IReadOnlyList items, bool clearPlaylist = false, bool clearPlayingItem = false) { PlaylistItemIds = items ?? Array.Empty(); diff --git a/MediaBrowser.Model/Configuration/MetadataOptions.cs b/MediaBrowser.Model/Configuration/MetadataOptions.cs index 76b72bd08e8..384a7997fdc 100644 --- a/MediaBrowser.Model/Configuration/MetadataOptions.cs +++ b/MediaBrowser.Model/Configuration/MetadataOptions.cs @@ -1,5 +1,5 @@ #nullable disable -#pragma warning disable CS1591 +#pragma warning disable CS1591, CA1819 using System; diff --git a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs index 806877ff0bd..94071b4196f 100644 --- a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs +++ b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs @@ -5,7 +5,7 @@ using System; namespace MediaBrowser.Model.Dlna { - public class ResolutionNormalizer + public static class ResolutionNormalizer { private static readonly ResolutionConfiguration[] Configurations = new[] diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 111070d813b..3634d070583 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -1,5 +1,5 @@ #nullable disable -#pragma warning disable CS1591 +#pragma warning disable CS1591, CA1819 using System; using System.Xml.Serialization; diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 3d866cdc2bd..52611c427ee 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -29,8 +29,6 @@ net5.0 false true - true - AllEnabledByDefault ../jellyfin.ruleset disable diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 68fb9064e40..dfb99117047 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -1,9 +1,6 @@ - - - @@ -57,12 +54,19 @@ + + + + + + + diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs new file mode 100644 index 00000000000..7c4d0cf53dd --- /dev/null +++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs @@ -0,0 +1,83 @@ +using System; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using AutoFixture; +using AutoFixture.AutoMoq; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.MediaEncoding.Subtitles; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Jellyfin.MediaEncoding.Subtitles.Tests +{ + public class SubtitleEncoderTests + { + internal static TheoryData GetReadableFile_Valid_TestData() + { + var data = new TheoryData(); + + data.Add( + new MediaSourceInfo() + { + Protocol = MediaProtocol.File + }, + new MediaStream() + { + Path = "/media/sub.ass", + IsExternal = true + }, + new SubtitleEncoder.SubtitleInfo("/media/sub.ass", MediaProtocol.File, "ass", true)); + + data.Add( + new MediaSourceInfo() + { + Protocol = MediaProtocol.File + }, + new MediaStream() + { + Path = "/media/sub.ssa", + IsExternal = true + }, + new SubtitleEncoder.SubtitleInfo("/media/sub.ssa", MediaProtocol.File, "ssa", true)); + + data.Add( + new MediaSourceInfo() + { + Protocol = MediaProtocol.File + }, + new MediaStream() + { + Path = "/media/sub.srt", + IsExternal = true + }, + new SubtitleEncoder.SubtitleInfo("/media/sub.srt", MediaProtocol.File, "srt", true)); + + return data; + } + + [Theory] + [MemberData(nameof(GetReadableFile_Valid_TestData))] + internal async Task GetReadableFile_Valid_Success(MediaSourceInfo mediaSource, MediaStream subtitleStream, SubtitleEncoder.SubtitleInfo subtitleInfo) + { + var fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true }); + var subtitleEncoder = fixture.Create(); + var result = await subtitleEncoder.GetReadableFile(mediaSource, subtitleStream, CancellationToken.None).ConfigureAwait(false); + Assert.Equal(subtitleInfo.Path, result.Path); + Assert.Equal(subtitleInfo.Protocol, result.Protocol); + Assert.Equal(subtitleInfo.Format, result.Format); + Assert.Equal(subtitleInfo.IsExternal, result.IsExternal); + } + } +} From b0194bce6c0b5ede46b2f193539600c929441678 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 18 Sep 2021 15:31:45 +0200 Subject: [PATCH 099/278] Add regression test for issue #5168 --- .../Subtitles/SubtitleEncoderTests.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs index 7c4d0cf53dd..5fe2c844716 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs @@ -64,6 +64,18 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests }, new SubtitleEncoder.SubtitleInfo("/media/sub.srt", MediaProtocol.File, "srt", true)); + data.Add( + new MediaSourceInfo() + { + Protocol = MediaProtocol.Http + }, + new MediaStream() + { + Path = "/media/sub.ass", + IsExternal = true + }, + new SubtitleEncoder.SubtitleInfo("/media/sub.ass", MediaProtocol.File, "ass", true)); + return data; } From d283702cac33bf9e8105431a26dd734647ddb371 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 18 Sep 2021 16:21:54 +0200 Subject: [PATCH 100/278] Fix Azure CI --- .ci/azure-pipelines-main.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml index e9b93baafe5..d49f6e5be5c 100644 --- a/.ci/azure-pipelines-main.yml +++ b/.ci/azure-pipelines-main.yml @@ -93,8 +93,8 @@ jobs: artifactName: 'Jellyfin.Common' - task: PublishPipelineArtifact@1 - displayName: 'Publish Artifact Extensions' - condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) - inputs: - targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Jellyfin.Extensions.dll' - artifactName: 'Jellyfin.Extensions' + displayName: 'Publish Artifact Extensions' + condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) + inputs: + targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Jellyfin.Extensions.dll' + artifactName: 'Jellyfin.Extensions' From a6d1e542e62548f177523f0acd67260f58066731 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 19 Sep 2021 20:53:31 +0200 Subject: [PATCH 101/278] Reduce allocations --- Emby.Dlna/Eventing/DlnaEventManager.cs | 5 +- .../LiveTv/Listings/XmlTvListingsProvider.cs | 7 ++- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 59 +++++++++---------- .../LiveTv/TunerHosts/M3uParser.cs | 2 +- .../Controllers/RemoteImageController.cs | 3 +- Jellyfin.Api/Helpers/StreamingHelpers.cs | 3 +- .../MediaEncoding/JobLogger.cs | 4 +- .../Parsers/BaseItemXmlParser.cs | 3 +- .../Probing/ProbeResultNormalizer.cs | 2 +- .../Subtitles/VttWriter.cs | 4 +- MediaBrowser.Model/Net/MimeTypes.cs | 3 +- .../Parsers/BaseNfoParser.cs | 3 +- src/Jellyfin.Extensions/StringExtensions.cs | 34 +++++++++++ .../StringExtensionsTests.cs | 21 +++++++ 14 files changed, 105 insertions(+), 48 deletions(-) diff --git a/Emby.Dlna/Eventing/DlnaEventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs index 3c91360904a..b39bd5ce9b5 100644 --- a/Emby.Dlna/Eventing/DlnaEventManager.cs +++ b/Emby.Dlna/Eventing/DlnaEventManager.cs @@ -11,6 +11,7 @@ using System.Net.Http; using System.Net.Mime; using System.Text; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using Microsoft.Extensions.Logging; @@ -82,9 +83,7 @@ namespace Emby.Dlna.Eventing if (!string.IsNullOrEmpty(header)) { // Starts with SECOND- - header = header.Split('-')[^1]; - - if (int.TryParse(header, NumberStyles.Integer, _usCulture, out var val)) + if (int.TryParse(header.AsSpan().RightPart('-'), NumberStyles.Integer, _usCulture, out var val)) { return val; } diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 8202fab861b..cb9801c1786 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using Jellyfin.XmlTv; using Jellyfin.XmlTv.Entities; using MediaBrowser.Common.Extensions; @@ -89,11 +90,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings return UnzipIfNeeded(path, cacheFile); } - private string UnzipIfNeeded(string originalUrl, string file) + private string UnzipIfNeeded(ReadOnlySpan originalUrl, string file) { - string ext = Path.GetExtension(originalUrl.Split('?')[0]); + ReadOnlySpan ext = Path.GetExtension(originalUrl.LeftPart('?')); - if (string.Equals(ext, ".gz", StringComparison.OrdinalIgnoreCase)) + if (ext.Equals(".gz", StringComparison.OrdinalIgnoreCase)) { try { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 2bd12a9c8f0..4d538c6043d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -36,7 +36,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly IHttpClientFactory _httpClientFactory; private readonly IServerApplicationHost _appHost; private readonly ISocketFactory _socketFactory; - private readonly INetworkManager _networkManager; private readonly IStreamHelper _streamHelper; private readonly JsonSerializerOptions _jsonOptions; @@ -50,7 +49,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun IHttpClientFactory httpClientFactory, IServerApplicationHost appHost, ISocketFactory socketFactory, - INetworkManager networkManager, IStreamHelper streamHelper, IMemoryCache memoryCache) : base(config, logger, fileSystem, memoryCache) @@ -58,7 +56,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _httpClientFactory = httpClientFactory; _appHost = appHost; _socketFactory = socketFactory; - _networkManager = networkManager; _streamHelper = streamHelper; _jsonOptions = JsonDefaults.Options; @@ -70,7 +67,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun protected override string ChannelIdPrefix => "hdhr_"; - private string GetChannelId(TunerHostInfo info, Channels i) + private string GetChannelId(Channels i) => ChannelIdPrefix + i.GuideNumber; internal async Task> GetLineup(TunerHostInfo info, CancellationToken cancellationToken) @@ -103,7 +100,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { Name = i.GuideName, Number = i.GuideNumber, - Id = GetChannelId(tuner, i), + Id = GetChannelId(i), IsFavorite = i.Favorite, TunerHostId = tuner.Id, IsHD = i.HD, @@ -255,7 +252,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); - var tuners = new List(); + var tuners = new List(model.TunerCount); var uri = new Uri(GetApiUrl(info)); @@ -264,10 +261,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun // Legacy HdHomeruns are IPv4 only var ipInfo = IPAddress.Parse(uri.Host); - for (int i = 0; i < model.TunerCount; ++i) + for (int i = 0; i < model.TunerCount; i++) { var name = string.Format(CultureInfo.InvariantCulture, "Tuner {0}", i + 1); - var currentChannel = "none"; // @todo Get current channel and map back to Station Id + var currentChannel = "none"; // TODO: Get current channel and map back to Station Id var isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false); var status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv; tuners.Add(new LiveTvTunerInfo @@ -455,28 +452,28 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun Path = url, Protocol = MediaProtocol.Udp, MediaStreams = new List - { - new MediaStream - { - Type = MediaStreamType.Video, - // Set the index to -1 because we don't know the exact index of the video stream within the container - Index = -1, - IsInterlaced = isInterlaced, - Codec = videoCodec, - Width = width, - Height = height, - BitRate = videoBitrate, - NalLengthSize = nal - }, - new MediaStream - { - Type = MediaStreamType.Audio, - // Set the index to -1 because we don't know the exact index of the audio stream within the container - Index = -1, - Codec = audioCodec, - BitRate = audioBitrate - } - }, + { + new MediaStream + { + Type = MediaStreamType.Video, + // Set the index to -1 because we don't know the exact index of the video stream within the container + Index = -1, + IsInterlaced = isInterlaced, + Codec = videoCodec, + Width = width, + Height = height, + BitRate = videoBitrate, + NalLengthSize = nal + }, + new MediaStream + { + Type = MediaStreamType.Audio, + // Set the index to -1 because we don't know the exact index of the audio stream within the container + Index = -1, + Codec = audioCodec, + BitRate = audioBitrate + } + }, RequiresOpening = true, RequiresClosing = true, BufferMs = 0, @@ -551,7 +548,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - var profile = streamId.Split('_')[0]; + var profile = streamId.AsSpan().LeftPart('_').ToString(); Logger.LogInformation("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channel.Id, streamId, profile); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 23071a4306f..506ef5548ab 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -238,7 +238,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { try { - numberString = Path.GetFileNameWithoutExtension(mediaUrl.Split('/')[^1]); + numberString = Path.GetFileNameWithoutExtension(mediaUrl.AsSpan().RightPart('/')).ToString(); if (!IsValidChannelNumber(numberString)) { diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs index 7a2c23991be..bcb2b50c7a3 100644 --- a/Jellyfin.Api/Controllers/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/RemoteImageController.cs @@ -7,6 +7,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Constants; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -199,7 +200,7 @@ namespace Jellyfin.Api.Controllers throw new ResourceNotFoundException(nameof(response.Content.Headers.ContentType)); } - var ext = response.Content.Headers.ContentType.MediaType.Split('/')[^1]; + var ext = response.Content.Headers.ContentType.MediaType.AsSpan().RightPart('/').ToString(); var fullCachePath = GetFullCachePath(urlHash + "." + ext); var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid."); diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 0041251e3d0..4fc791665e4 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Models.StreamingDtos; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; @@ -81,7 +82,7 @@ namespace Jellyfin.Api.Helpers throw new ResourceNotFoundException(nameof(httpRequest.Path)); } - var url = httpRequest.Path.Value.Split('.')[^1]; + var url = httpRequest.Path.Value.AsSpan().RightPart('.').ToString(); if (string.IsNullOrEmpty(streamingRequest.AudioCodec)) { diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs index aa5e2c4038e..c4ddc5618dd 100644 --- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs +++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs @@ -120,7 +120,7 @@ namespace MediaBrowser.Controller.MediaEncoding var size = part.Split('=', 2)[^1]; int? scale = null; - if (size.IndexOf("kb", StringComparison.OrdinalIgnoreCase) != -1) + if (size.Contains("kb", StringComparison.OrdinalIgnoreCase)) { scale = 1024; size = size.Replace("kb", string.Empty, StringComparison.OrdinalIgnoreCase); @@ -139,7 +139,7 @@ namespace MediaBrowser.Controller.MediaEncoding var rate = part.Split('=', 2)[^1]; int? scale = null; - if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1) + if (rate.Contains("kbits/s", StringComparison.OrdinalIgnoreCase)) { scale = 1024; rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase); diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index ef130ee7472..9103bf64768 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Xml; +using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; @@ -331,7 +332,7 @@ namespace MediaBrowser.LocalMetadata.Parsers if (!string.IsNullOrWhiteSpace(text)) { - if (int.TryParse(text.Split(' ')[0], NumberStyles.Integer, _usCulture, out var runtime)) + if (int.TryParse(text.AsSpan().LeftPart(' '), NumberStyles.Integer, _usCulture, out var runtime)) { item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks; } diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 2516aad1cc2..c377f1720d0 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -1378,7 +1378,7 @@ namespace MediaBrowser.MediaEncoding.Probing { var disc = tags.GetValueOrDefault(tagName); - if (!string.IsNullOrEmpty(disc) && int.TryParse(disc.Split('/')[0], out var discNum)) + if (!string.IsNullOrEmpty(disc) && int.TryParse(disc.AsSpan().LeftPart('/'), out var discNum)) { return discNum; } diff --git a/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs index ad32cb7943b..6d56dda91fa 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs @@ -18,14 +18,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) { writer.WriteLine("WEBVTT"); - writer.WriteLine(string.Empty); + writer.WriteLine(); writer.WriteLine("REGION"); writer.WriteLine("id:subtitle"); writer.WriteLine("width:80%"); writer.WriteLine("lines:3"); writer.WriteLine("regionanchor:50%,100%"); writer.WriteLine("viewportanchor:50%,90%"); - writer.WriteLine(string.Empty); + writer.WriteLine(); foreach (var trackEvent in info.TrackEvents) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index 96f5ab51aef..7b3c17c85ad 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Jellyfin.Extensions; namespace MediaBrowser.Model.Net { @@ -221,7 +222,7 @@ namespace MediaBrowser.Model.Net } // handle text/html; charset=UTF-8 - mimeType = mimeType.Split(';')[0]; + mimeType = mimeType.AsSpan().LeftPart(';').ToString(); if (_extensionLookup.TryGetValue(mimeType, out string? result)) { diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index f975278fbc1..b3efb8634f7 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Xml; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Providers; using MediaBrowser.Controller.Entities; @@ -474,7 +475,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(text)) { - if (int.TryParse(text.Split(' ')[0], NumberStyles.Integer, UsCulture, out var runtime)) + if (int.TryParse(text.AsSpan().LeftPart(' '), NumberStyles.Integer, UsCulture, out var runtime)) { item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks; } diff --git a/src/Jellyfin.Extensions/StringExtensions.cs b/src/Jellyfin.Extensions/StringExtensions.cs index acc695ed2fd..3a77072539a 100644 --- a/src/Jellyfin.Extensions/StringExtensions.cs +++ b/src/Jellyfin.Extensions/StringExtensions.cs @@ -27,5 +27,39 @@ namespace Jellyfin.Extensions return count; } + + /// + /// Returns the part on the left of the needle. + /// + /// The string to seek. + /// The needle to find. + /// The part left of the . + public static ReadOnlySpan LeftPart(this ReadOnlySpan haystack, char needle) + { + var pos = haystack.IndexOf(needle); + return pos == -1 ? haystack : haystack[..pos]; + } + + /// + /// Returns the part on the right of the needle. + /// + /// The string to seek. + /// The needle to find. + /// The part right of the . + public static ReadOnlySpan RightPart(this ReadOnlySpan haystack, char needle) + { + var pos = haystack.LastIndexOf(needle); + if (pos == -1) + { + return haystack; + } + + if (pos == haystack.Length - 1) + { + return ReadOnlySpan.Empty; + } + + return haystack[(pos + 1)..]; + } } } diff --git a/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs b/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs index d1aa2e47649..17671d13b02 100644 --- a/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs +++ b/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs @@ -14,5 +14,26 @@ namespace Jellyfin.Extensions.Tests { Assert.Equal(count, str.AsSpan().Count(needle)); } + + [Theory] + [InlineData("", 'q', "")] + [InlineData("Banana split", ' ', "Banana")] + [InlineData("Banana split", 'q', "Banana split")] + public void LeftPart_ValidArgsCharNeedle_Correct(string str, char needle, string expectedResult) + { + var result = str.AsSpan().LeftPart(needle).ToString(); + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData("", 'q', "")] + [InlineData("Banana split", ' ', "split")] + [InlineData("Banana split", 'q', "Banana split")] + [InlineData("Banana split.", '.', "")] + public void RightPart_ValidArgsCharNeedle_Correct(string str, char needle, string expectedResult) + { + var result = str.AsSpan().RightPart(needle).ToString(); + Assert.Equal(expectedResult, result); + } } } From 9148820d89ff58b53c8fa6d8ced33c025187dd12 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 19 Sep 2021 21:26:00 +0200 Subject: [PATCH 102/278] Add more tests --- tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs b/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs index 17671d13b02..7186cc0236c 100644 --- a/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs +++ b/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs @@ -19,6 +19,7 @@ namespace Jellyfin.Extensions.Tests [InlineData("", 'q', "")] [InlineData("Banana split", ' ', "Banana")] [InlineData("Banana split", 'q', "Banana split")] + [InlineData("Banana split 2", ' ', "Banana")] public void LeftPart_ValidArgsCharNeedle_Correct(string str, char needle, string expectedResult) { var result = str.AsSpan().LeftPart(needle).ToString(); @@ -30,6 +31,7 @@ namespace Jellyfin.Extensions.Tests [InlineData("Banana split", ' ', "split")] [InlineData("Banana split", 'q', "Banana split")] [InlineData("Banana split.", '.', "")] + [InlineData("Banana split 2", ' ', "2")] public void RightPart_ValidArgsCharNeedle_Correct(string str, char needle, string expectedResult) { var result = str.AsSpan().RightPart(needle).ToString(); From e7d6c4550989e960bba8f94f68da1cbc1b7b2006 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 19 Sep 2021 15:06:27 -0600 Subject: [PATCH 103/278] Upgrade to dotnet 5.0.10 --- .../Emby.Server.Implementations.csproj | 2 +- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- .../Jellyfin.Server.Implementations.csproj | 8 ++++---- Jellyfin.Server/Jellyfin.Server.csproj | 4 ++-- deployment/Dockerfile.ubuntu.amd64 | 2 +- deployment/Dockerfile.ubuntu.arm64 | 2 +- deployment/Dockerfile.ubuntu.armhf | 2 +- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 2 +- tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index e48dbcd191f..fda008397c7 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -29,7 +29,7 @@ - + diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 2fca88f24ed..7f4eb0378ad 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -14,7 +14,7 @@ - + diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index a75b285936c..434c414a4a7 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -19,13 +19,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 1fdad73b74b..fc935cecb23 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -33,8 +33,8 @@ - - + + diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64 index d88efcdc95d..6b18e758360 100644 --- a/deployment/Dockerfile.ubuntu.amd64 +++ b/deployment/Dockerfile.ubuntu.amd64 @@ -19,7 +19,7 @@ RUN apt-get update -yqq \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/13b9d84c-a35b-4ffe-8f62-447a01403d64/1f9ae31daa0f7d98513e7551246899f2/dotnet-sdk-5.0.400-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5972698f-ba44-4664-9c50-bdc69ca70fb7/1cf7d94425d8dd4d5789dfa978d61475/dotnet-sdk-5.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64 index 4f41bba2d9a..50b116a6778 100644 --- a/deployment/Dockerfile.ubuntu.arm64 +++ b/deployment/Dockerfile.ubuntu.arm64 @@ -18,7 +18,7 @@ RUN apt-get update -yqq \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/13b9d84c-a35b-4ffe-8f62-447a01403d64/1f9ae31daa0f7d98513e7551246899f2/dotnet-sdk-5.0.400-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5972698f-ba44-4664-9c50-bdc69ca70fb7/1cf7d94425d8dd4d5789dfa978d61475/dotnet-sdk-5.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf index 01752d53677..1b1e252f78c 100644 --- a/deployment/Dockerfile.ubuntu.armhf +++ b/deployment/Dockerfile.ubuntu.armhf @@ -18,7 +18,7 @@ RUN apt-get update -yqq \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/13b9d84c-a35b-4ffe-8f62-447a01403d64/1f9ae31daa0f7d98513e7551246899f2/dotnet-sdk-5.0.400-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5972698f-ba44-4664-9c50-bdc69ca70fb7/1cf7d94425d8dd4d5789dfa978d61475/dotnet-sdk-5.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 0c36e81cca6..9dac63e7036 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 592b444c99a..42e60df5fb6 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -9,7 +9,7 @@ - + diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index f249be674c2..4f0bbc36cff 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -10,7 +10,7 @@ - + From dc8420c7a20f462bdd3b02d7f20743c3ada66ecb Mon Sep 17 00:00:00 2001 From: Dixin Date: Sun, 19 Sep 2021 16:54:00 -0700 Subject: [PATCH 104/278] Fix extra folder type resolving. --- .../Library/CoreResolutionIgnoreRule.cs | 4 +- .../Library/LibraryManager.cs | 4 +- .../Library/Resolvers/TV/EpisodeResolver.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 76 +++++++------------ 4 files changed, 34 insertions(+), 52 deletions(-) diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index c7d11396393..bc5b4499fcb 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.Library if (parent != null) { // Ignore trailer folders but allow it at the collection level - if (string.Equals(filename, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase) + if (string.Equals(filename, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase) && !(parent is AggregateFolder) && !(parent is UserRootFolder)) { @@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Library if (parent != null) { // Don't resolve these into audio files - if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFilename, StringComparison.Ordinal) + if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFileName, StringComparison.Ordinal) && _libraryManager.IsAudioFile(filename)) { return true; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 8054beae3f4..6f0f3d080dc 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2714,7 +2714,7 @@ namespace Emby.Server.Implementations.Library var namingOptions = GetNamingOptions(); var files = owner.IsInMixedFolder ? new List() : fileSystemChildren.Where(i => i.IsDirectory) - .Where(i => string.Equals(i.Name, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase)) + .Where(i => string.Equals(i.Name, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase)) .SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false)) .ToList(); @@ -2758,7 +2758,7 @@ namespace Emby.Server.Implementations.Library var namingOptions = GetNamingOptions(); var files = owner.IsInMixedFolder ? new List() : fileSystemChildren.Where(i => i.IsDirectory) - .Where(i => BaseItem.AllExtrasTypesFolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + .Where(i => BaseItem.AllExtrasTypesFolderNames.ContainsKey(i.Name ?? string.Empty)) .SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false)) .ToList(); diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index d6ae9105659..cf2d22f4df4 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV if ((season != null || string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || args.HasParent()) - && (parent is Series || !BaseItem.AllExtrasTypesFolderNames.Contains(parent.Name, StringComparer.OrdinalIgnoreCase))) + && (parent is Series || !BaseItem.AllExtrasTypesFolderNames.ContainsKey(parent.Name))) { var episode = ResolveVideo(args, false); diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index f4c91973bdf..1996ab618e0 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -44,18 +44,10 @@ namespace MediaBrowser.Controller.Entities /// /// The trailer folder name. /// - public const string TrailerFolderName = "trailers"; + public const string TrailersFolderName = "trailers"; public const string ThemeSongsFolderName = "theme-music"; - public const string ThemeSongFilename = "theme"; + public const string ThemeSongFileName = "theme"; public const string ThemeVideosFolderName = "backdrops"; - public const string ExtrasFolderName = "extras"; - public const string BehindTheScenesFolderName = "behind the scenes"; - public const string DeletedScenesFolderName = "deleted scenes"; - public const string InterviewFolderName = "interviews"; - public const string SceneFolderName = "scenes"; - public const string SampleFolderName = "samples"; - public const string ShortsFolderName = "shorts"; - public const string FeaturettesFolderName = "featurettes"; /// /// The supported image extensions. @@ -93,16 +85,20 @@ namespace MediaBrowser.Controller.Entities }; public static readonly char[] SlugReplaceChars = { '?', '/', '&' }; - public static readonly string[] AllExtrasTypesFolderNames = + + /// + /// The supported extra folder names and types. See . + /// + public static readonly Dictionary AllExtrasTypesFolderNames = new Dictionary(StringComparer.OrdinalIgnoreCase) { - ExtrasFolderName, - BehindTheScenesFolderName, - DeletedScenesFolderName, - InterviewFolderName, - SceneFolderName, - SampleFolderName, - ShortsFolderName, - FeaturettesFolderName + ["extras"] = MediaBrowser.Model.Entities.ExtraType.Unknown, + ["behind the scenes"] = MediaBrowser.Model.Entities.ExtraType.BehindTheScenes, + ["deleted scenes"] = MediaBrowser.Model.Entities.ExtraType.DeletedScene, + ["interviews"] = MediaBrowser.Model.Entities.ExtraType.Interview, + ["scenes"] = MediaBrowser.Model.Entities.ExtraType.Scene, + ["samples"] = MediaBrowser.Model.Entities.ExtraType.Sample, + ["shorts"] = MediaBrowser.Model.Entities.ExtraType.Clip, + ["featurettes"] = MediaBrowser.Model.Entities.ExtraType.Clip }; private string _sortName; @@ -1358,7 +1354,7 @@ namespace MediaBrowser.Controller.Entities // Support plex/xbmc convention files.AddRange(fileSystemChildren - .Where(i => !i.IsDirectory && System.IO.Path.GetFileNameWithoutExtension(i.FullName.AsSpan()).Equals(ThemeSongFilename, StringComparison.OrdinalIgnoreCase))); + .Where(i => !i.IsDirectory && System.IO.Path.GetFileNameWithoutExtension(i.FullName.AsSpan()).Equals(ThemeSongFileName, StringComparison.OrdinalIgnoreCase))); return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions()) .OfType() @@ -1417,39 +1413,25 @@ namespace MediaBrowser.Controller.Entities protected virtual BaseItem[] LoadExtras(List fileSystemChildren, IDirectoryService directoryService) { - var extras = new List /// The filename. /// DlnaIconResponse. - ImageStream GetIcon(string filename); + ImageStream? GetIcon(string filename); } } diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index c7f61a90bb7..7ca0e851bdc 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -58,7 +58,7 @@ namespace MediaBrowser.Controller.Drawing /// Guid. string GetImageCacheTag(BaseItem item, ItemImageInfo image); - string GetImageCacheTag(BaseItem item, ChapterInfo info); + string GetImageCacheTag(BaseItem item, ChapterInfo chapter); string? GetImageCacheTag(User user); diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 0f697bcccdb..47cec7d77f3 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -32,7 +32,7 @@ - net5.0 + net6.0 false true true diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index b09b7dba6cb..e92c4a08a61 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -541,7 +541,12 @@ namespace MediaBrowser.Controller.MediaEncoding return MimeType; } - return MimeTypes.GetMimeType(outputPath, enableStreamDefault); + if (enableStreamDefault) + { + return MimeTypes.GetMimeType(outputPath); + } + + return MimeTypes.GetMimeType(outputPath, null); } public bool DeInterlace(string videoCodec, bool forceDeinterlaceIfSourceIsInterlaced) diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj index 1cf8fcd1b5b..a3db717b9f9 100644 --- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj +++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj @@ -11,7 +11,7 @@ - net5.0 + net6.0 false true diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 5deaecc9520..30cfb904e1d 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -6,7 +6,7 @@ - net5.0 + net6.0 false true diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs index 3d864e29ca6..20e4be78026 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles subRip.LoadSubtitle(subtitle, lines, "untitled"); if (subRip.ErrorCount > 0) { - _logger.LogError("{ErrorCount} errors encountered while parsing subtitle."); + _logger.LogError("{ErrorCount} errors encountered while parsing subtitle.", subRip.ErrorCount); } var trackInfo = new SubtitleTrackInfo(); diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index a371afc2cfc..b0a12a9c900 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -14,7 +14,7 @@ - net5.0 + net6.0 false true true diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index 7b3c17c85ad..1d9150f02b8 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Jellyfin.Extensions; @@ -164,15 +165,16 @@ namespace MediaBrowser.Model.Net return dict; } - public static string? GetMimeType(string path) => GetMimeType(path, true); + public static string GetMimeType(string path) => GetMimeType(path, "application/octet-stream"); /// /// Gets the type of the MIME. /// /// The filename to find the MIME type of. - /// Whether of not to return a default value if no fitting MIME type is found. - /// The worrect MIME type for the given filename, or `null` if it wasn't found and is false. - public static string? GetMimeType(string filename, bool enableStreamDefault) + /// Theefault value to return if no fitting MIME type is found. + /// The correct MIME type for the given filename, or if it wasn't found. + [return: NotNullIfNotNullAttribute("defaultValue")] + public static string? GetMimeType(string filename, string? defaultValue = null) { if (filename.Length == 0) { @@ -211,7 +213,7 @@ namespace MediaBrowser.Model.Net return "application/octet-stream"; } - return enableStreamDefault ? "application/octet-stream" : null; + return defaultValue; } public static string? ToExtension(string mimeType) diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 3a6e1627464..29d6b01f237 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -26,7 +26,7 @@ - net5.0 + net6.0 false true ../jellyfin.ruleset diff --git a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs index 268538815e6..19d90b9a1b2 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb if (reader.TokenType == JsonTokenType.String) { var str = reader.GetString(); - if (str != null && str.Equals("N/A", StringComparison.OrdinalIgnoreCase)) + if (str == null || str.Equals("N/A", StringComparison.OrdinalIgnoreCase)) { return null; } diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj index 3e2a9bacf12..926be5a9271 100644 --- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj +++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj @@ -15,7 +15,7 @@ - net5.0 + net6.0 false true diff --git a/RSSDP/DisposableManagedObjectBase.cs b/RSSDP/DisposableManagedObjectBase.cs index 7d6a471f953..5d7da4124e7 100644 --- a/RSSDP/DisposableManagedObjectBase.cs +++ b/RSSDP/DisposableManagedObjectBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Text; namespace Rssdp.Infrastructure @@ -45,11 +46,11 @@ namespace Rssdp.Infrastructure const string ArgFormat = "{0}: {1}\r\n"; - builder.AppendFormat("{0}\r\n", header); + builder.AppendFormat(CultureInfo.InvariantCulture, "{0}\r\n", header); foreach (var pair in values) { - builder.AppendFormat(ArgFormat, pair.Key, pair.Value); + builder.AppendFormat(CultureInfo.InvariantCulture, ArgFormat, pair.Key, pair.Value); } builder.Append("\r\n"); diff --git a/RSSDP/RSSDP.csproj b/RSSDP/RSSDP.csproj index 54113d46443..77130983b5d 100644 --- a/RSSDP/RSSDP.csproj +++ b/RSSDP/RSSDP.csproj @@ -11,7 +11,7 @@ - net5.0 + net6.0 false AllDisabledByDefault disable diff --git a/RSSDP/SsdpDevice.cs b/RSSDP/SsdpDevice.cs index 4005d836d98..c826830f1da 100644 --- a/RSSDP/SsdpDevice.cs +++ b/RSSDP/SsdpDevice.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Globalization; using Rssdp.Infrastructure; namespace Rssdp @@ -134,11 +135,13 @@ namespace Rssdp { get { - return String.Format("urn:{0}:{3}:{1}:{2}", - this.DeviceTypeNamespace ?? String.Empty, - this.DeviceType ?? String.Empty, - this.DeviceVersion, - this.DeviceClass ?? "device"); + return String.Format( + CultureInfo.InvariantCulture, + "urn:{0}:{3}:{1}:{2}", + this.DeviceTypeNamespace ?? String.Empty, + this.DeviceType ?? String.Empty, + this.DeviceVersion, + this.DeviceClass ?? "device"); } } diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs index c9e795d565b..64d19803df2 100644 --- a/RSSDP/SsdpDevicePublisher.cs +++ b/RSSDP/SsdpDevicePublisher.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Globalization; using System.Linq; using System.Net; using System.Threading; @@ -233,7 +234,7 @@ namespace Rssdp.Infrastructure { if (String.IsNullOrEmpty(searchTarget)) { - WriteTrace(String.Format("Invalid search request received From {0}, Target is null/empty.", remoteEndPoint.ToString())); + WriteTrace(String.Format(CultureInfo.InvariantCulture, "Invalid search request received From {0}, Target is null/empty.", remoteEndPoint.ToString())); return; } @@ -340,7 +341,7 @@ namespace Rssdp.Infrastructure private string GetUsn(string udn, string fullDeviceType) { - return String.Format("{0}::{1}", udn, fullDeviceType); + return String.Format(CultureInfo.InvariantCulture, "{0}::{1}", udn, fullDeviceType); } private async void SendSearchResponse( @@ -363,7 +364,7 @@ namespace Rssdp.Infrastructure values["DATE"] = DateTime.UtcNow.ToString("r"); values["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds; values["ST"] = searchTarget; - values["SERVER"] = string.Format("{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, ServerVersion); + values["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, ServerVersion); values["USN"] = uniqueServiceName; values["LOCATION"] = rootDevice.Location.ToString(); @@ -497,7 +498,7 @@ namespace Rssdp.Infrastructure values["DATE"] = DateTime.UtcNow.ToString("r"); values["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds; values["LOCATION"] = rootDevice.Location.ToString(); - values["SERVER"] = string.Format("{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, ServerVersion); + values["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, ServerVersion); values["NTS"] = "ssdp:alive"; values["NT"] = notificationType; values["USN"] = uniqueServiceName; @@ -522,7 +523,7 @@ namespace Rssdp.Infrastructure } tasks.Add(SendByeByeNotification(device, device.Udn, device.Udn, cancellationToken)); - tasks.Add(SendByeByeNotification(device, String.Format("urn:{0}", device.FullDeviceType), GetUsn(device.Udn, device.FullDeviceType), cancellationToken)); + tasks.Add(SendByeByeNotification(device, String.Format(CultureInfo.InvariantCulture, "urn:{0}", device.FullDeviceType), GetUsn(device.Udn, device.FullDeviceType), cancellationToken)); foreach (var childDevice in device.Devices) { @@ -542,7 +543,7 @@ namespace Rssdp.Infrastructure // If needed later for non-server devices, these headers will need to be dynamic values["HOST"] = "239.255.255.250:1900"; values["DATE"] = DateTime.UtcNow.ToString("r"); - values["SERVER"] = string.Format("{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, ServerVersion); + values["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, ServerVersion); values["NTS"] = "ssdp:byebye"; values["NT"] = notificationType; values["USN"] = uniqueServiceName; @@ -550,7 +551,7 @@ namespace Rssdp.Infrastructure var message = BuildMessage(header, values); var sendCount = IsDisposed ? 1 : 3; - WriteTrace(String.Format("Sent byebye notification"), device); + WriteTrace(String.Format(CultureInfo.InvariantCulture, "Sent byebye notification"), device); return _CommsServer.SendMulticastMessage(message, sendCount, _sendOnlyMatchedHost ? device.ToRootDevice().Address : null, cancellationToken); } diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 9dac63e7036..b52ea078af0 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -6,7 +6,7 @@ - net5.0 + net6.0 false ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index 1619fa89c87..1fe4e256561 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -6,7 +6,7 @@ - net5.0 + net6.0 false ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj index a5778b59c8b..e9a95157106 100644 --- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -6,7 +6,7 @@ - net5.0 + net6.0 false ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj index 5a48631c29a..1fb95aab4d8 100644 --- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj +++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 false ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj index 20680157f52..2dc4ac19a5a 100644 --- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj +++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 false ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj index e9cd8c0623d..201f63a2d6b 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj @@ -6,7 +6,7 @@ - net5.0 + net6.0 false ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj index 09b8a7a9486..a37e5ac9205 100644 --- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj +++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 false ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj index a4ebab141e2..75d466198a0 100644 --- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -6,7 +6,7 @@ - net5.0 + net6.0 false ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj index 5fa2ecfe9ed..75d9b9ea900 100644 --- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj +++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj @@ -6,7 +6,7 @@ - net5.0 + net6.0 false ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 9b6ab7bdf5b..5ecd8460470 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -6,7 +6,7 @@ - net5.0 + net6.0 false ../jellyfin-tests.ruleset Jellyfin.Server.Implementations.Tests diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 42e60df5fb6..7939c7118ef 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -1,6 +1,6 @@ - net5.0 + net6.0 false ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 4f0bbc36cff..b30e690a5ef 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 false ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj index e085907583a..94294c8bf30 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj +++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 false ../jellyfin-tests.ruleset From 4d1d9f23d5edd248900118963874a7ab83d04aa1 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 23 May 2021 00:20:19 +0200 Subject: [PATCH 123/278] Use new Enum.TryParse(ReadOnlySpan) overload --- Emby.Dlna/DlnaManager.cs | 12 ++++++--- .../Data/SqliteItemRepository.cs | 27 ++++++++----------- .../Library/LibraryManager.cs | 4 +-- .../Drawing/ImageStream.cs | 9 +++++-- .../Jellyfin.Providers.Tests.csproj | 2 +- 5 files changed, 28 insertions(+), 26 deletions(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 38555043247..305e43a3ca3 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -493,11 +493,15 @@ namespace Emby.Dlna : ImageFormat.Jpg; var resource = GetType().Namespace + ".Images." + filename.ToLowerInvariant(); - - return new ImageStream + var stream = _assembly.GetManifestResourceStream(resource); + if (stream == null) { - Format = format, - Stream = _assembly.GetManifestResourceStream(resource) + return null; + } + + return new ImageStream(stream) + { + Format = format }; } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 88fc5018df9..88cd4476a71 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1150,7 +1150,7 @@ namespace Emby.Server.Implementations.Data return null; } - if (Enum.TryParse(imageType.ToString(), true, out ImageType type)) + if (Enum.TryParse(imageType, true, out ImageType type)) { image.Type = type; } @@ -1571,7 +1571,6 @@ namespace Emby.Server.Implementations.Data if (reader.TryGetString(index++, out var audioString)) { - // TODO Span overload coming in the future https://github.com/dotnet/runtime/issues/1916 if (Enum.TryParse(audioString, true, out ProgramAudio audio)) { item.Audio = audio; @@ -1610,18 +1609,16 @@ namespace Emby.Server.Implementations.Data { if (reader.TryGetString(index++, out var lockedFields)) { - IEnumerable GetLockedFields(string s) + List fields = null; + foreach (var i in lockedFields.Split('|')) { - foreach (var i in s.Split('|', StringSplitOptions.RemoveEmptyEntries)) + if (Enum.TryParse(i, true, out MetadataField parsedValue)) { - if (Enum.TryParse(i, true, out MetadataField parsedValue)) - { - yield return parsedValue; - } + (fields ??= new List()).Add(parsedValue); } } - item.LockedFields = GetLockedFields(lockedFields).ToArray(); + item.LockedFields = fields?.ToArray() ?? Array.Empty(); } } @@ -1647,18 +1644,16 @@ namespace Emby.Server.Implementations.Data { if (reader.TryGetString(index, out var trailerTypes)) { - IEnumerable GetTrailerTypes(string s) + List types = null; + foreach (var i in trailerTypes.Split('|')) { - foreach (var i in s.Split('|', StringSplitOptions.RemoveEmptyEntries)) + if (Enum.TryParse(i, true, out TrailerType parsedValue)) { - if (Enum.TryParse(i, true, out TrailerType parsedValue)) - { - yield return parsedValue; - } + (types ??= new List()).Add(parsedValue); } } - trailer.TrailerTypes = GetTrailerTypes(trailerTypes).ToArray(); + trailer.TrailerTypes = types.ToArray() ?? Array.Empty(); } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 6f0f3d080dc..132486b4a67 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1250,10 +1250,8 @@ namespace Emby.Server.Implementations.Library private CollectionTypeOptions? GetCollectionType(string path) { var files = _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false); - foreach (var file in files) + foreach (ReadOnlySpan file in files) { - // TODO: @bond use a ReadOnlySpan here when Enum.TryParse supports it - // https://github.com/dotnet/runtime/issues/20008 if (Enum.TryParse(Path.GetFileNameWithoutExtension(file), true, out var res)) { return res; diff --git a/MediaBrowser.Controller/Drawing/ImageStream.cs b/MediaBrowser.Controller/Drawing/ImageStream.cs index 5d552170f9f..f4c30579936 100644 --- a/MediaBrowser.Controller/Drawing/ImageStream.cs +++ b/MediaBrowser.Controller/Drawing/ImageStream.cs @@ -8,11 +8,16 @@ namespace MediaBrowser.Controller.Drawing { public class ImageStream : IDisposable { + public ImageStream(Stream stream) + { + Stream = stream; + } + /// - /// Gets or sets the stream. + /// Gets the stream. /// /// The stream. - public Stream? Stream { get; set; } + public Stream Stream { get; } /// /// Gets or sets the format. diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj index d9e33617bcd..0b2db64b0bb 100644 --- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj +++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 false ../jellyfin-tests.ruleset From fb2f07dc84bfd75e72aaaa8d595c3267a52678de Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 21 Sep 2021 23:24:43 +0200 Subject: [PATCH 124/278] Replace RNGCryptoServiceProvider with RandomNumberGenerator --- .../QuickConnect/QuickConnectManager.cs | 28 ++----------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index ae773c6589d..c81c2699456 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -18,7 +18,7 @@ namespace Emby.Server.Implementations.QuickConnect /// /// Quick connect implementation. /// - public class QuickConnectManager : IQuickConnect, IDisposable + public class QuickConnectManager : IQuickConnect { /// /// The length of user facing codes. @@ -30,7 +30,6 @@ namespace Emby.Server.Implementations.QuickConnect /// private const int Timeout = 10; - private readonly RNGCryptoServiceProvider _rng = new (); private readonly ConcurrentDictionary _currentRequests = new (); private readonly ConcurrentDictionary _authorizedSecrets = new (); @@ -140,7 +139,7 @@ namespace Emby.Server.Implementations.QuickConnect uint scale = uint.MaxValue; while (scale == uint.MaxValue) { - _rng.GetBytes(raw); + RandomNumberGenerator.Fill(raw); scale = BitConverter.ToUInt32(raw); } @@ -199,31 +198,10 @@ namespace Emby.Server.Implementations.QuickConnect return result.AuthenticationResult; } - /// - /// Dispose. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Dispose. - /// - /// Dispose unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _rng.Dispose(); - } - } - private string GenerateSecureRandom(int length = 32) { Span bytes = stackalloc byte[length]; - _rng.GetBytes(bytes); + RandomNumberGenerator.Fill(bytes); return Convert.ToHexString(bytes); } From be00480fe20ae22b485badeaf002ef17614d01f3 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Tue, 21 Sep 2021 14:48:29 -0700 Subject: [PATCH 125/278] ci - target net6.0 (#6594) --- .ci/azure-pipelines-abi.yml | 2 +- .ci/azure-pipelines-main.yml | 2 +- .ci/azure-pipelines-package.yml | 4 ++-- .ci/azure-pipelines-test.yml | 2 +- .ci/azure-pipelines.yml | 2 -- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.ci/azure-pipelines-abi.yml b/.ci/azure-pipelines-abi.yml index e58a2bdc7e5..46bc75608eb 100644 --- a/.ci/azure-pipelines-abi.yml +++ b/.ci/azure-pipelines-abi.yml @@ -7,7 +7,7 @@ parameters: default: "ubuntu-latest" - name: DotNetSdkVersion type: string - default: 5.0.302 + default: 6.0.100-rc.1.21458.32 jobs: - job: CompatibilityCheck diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml index d49f6e5be5c..c07a93d44e1 100644 --- a/.ci/azure-pipelines-main.yml +++ b/.ci/azure-pipelines-main.yml @@ -1,7 +1,7 @@ parameters: LinuxImage: 'ubuntu-latest' RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj' - DotNetSdkVersion: 5.0.302 + DotNetSdkVersion: 6.0.100-rc.1.21458.32 jobs: - job: Build diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index 543fd7fc6de..b7b06e50551 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -195,10 +195,10 @@ jobs: steps: - task: UseDotNet@2 - displayName: 'Use .NET 5.0 sdk' + displayName: 'Use .NET 6.0 sdk' inputs: packageType: 'sdk' - version: '5.0.x' + version: '6.0.100-rc.1.21458.32' - task: DotNetCoreCLI@2 displayName: 'Build Stable Nuget packages' diff --git a/.ci/azure-pipelines-test.yml b/.ci/azure-pipelines-test.yml index 7ec4cdad1d6..99e305c740c 100644 --- a/.ci/azure-pipelines-test.yml +++ b/.ci/azure-pipelines-test.yml @@ -10,7 +10,7 @@ parameters: default: "tests/**/*Tests.csproj" - name: DotNetSdkVersion type: string - default: 5.0.302 + default: 6.0.100-rc.1.21458.32 jobs: - job: Test diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 98d9f3248d8..19c9caacbb6 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -5,8 +5,6 @@ variables: value: 'tests/**/*Tests.csproj' - name: RestoreBuildProjects value: 'Jellyfin.Server/Jellyfin.Server.csproj' -- name: DotNetSdkVersion - value: 5.0.302 pr: autoCancel: true From 086d5925c9d71355995e967b2e1d09c3eabacce1 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 22 Sep 2021 00:40:11 +0200 Subject: [PATCH 126/278] Update Jellyfin.Extensions to .Net6 --- src/Jellyfin.Extensions/Jellyfin.Extensions.csproj | 4 ++-- .../Json/Converters/JsonDelimitedArrayConverter.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj index 2d9ce06fe36..3d9538d1bb4 100644 --- a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj +++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 false true true @@ -22,7 +22,7 @@ $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - + diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs index c39805aa359..3c7d504b101 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs @@ -44,7 +44,7 @@ namespace Jellyfin.Extensions.Json.Converters { try { - parsedValues[i] = _typeConverter.ConvertFrom(stringEntries[i].Trim()); + parsedValues[i] = _typeConverter.ConvertFrom(stringEntries[i].Trim()) ?? throw new FormatException(); convertedCount++; } catch (FormatException) From 5fd315b17c7c95c05eaba0713b27f6a95d31e164 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 24 Sep 2021 20:15:10 +0200 Subject: [PATCH 127/278] Address comments --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 6 +++--- MediaBrowser.Model/Net/MimeTypes.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 88cd4476a71..0a48b844dd1 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1610,7 +1610,7 @@ namespace Emby.Server.Implementations.Data if (reader.TryGetString(index++, out var lockedFields)) { List fields = null; - foreach (var i in lockedFields.Split('|')) + foreach (var i in lockedFields.AsSpan().Split('|')) { if (Enum.TryParse(i, true, out MetadataField parsedValue)) { @@ -1645,7 +1645,7 @@ namespace Emby.Server.Implementations.Data if (reader.TryGetString(index, out var trailerTypes)) { List types = null; - foreach (var i in trailerTypes.Split('|')) + foreach (var i in trailerTypes.AsSpan().Split('|')) { if (Enum.TryParse(i, true, out TrailerType parsedValue)) { @@ -1653,7 +1653,7 @@ namespace Emby.Server.Implementations.Data } } - trailer.TrailerTypes = types.ToArray() ?? Array.Empty(); + trailer.TrailerTypes = types?.ToArray() ?? Array.Empty(); } } diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index 1d9150f02b8..748170a0e14 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -171,7 +171,7 @@ namespace MediaBrowser.Model.Net /// Gets the type of the MIME. /// /// The filename to find the MIME type of. - /// Theefault value to return if no fitting MIME type is found. + /// The default value to return if no fitting MIME type is found. /// The correct MIME type for the given filename, or if it wasn't found. [return: NotNullIfNotNullAttribute("defaultValue")] public static string? GetMimeType(string filename, string? defaultValue = null) From e627b1b154a48ccc18dd50c5214799b41dd02f7a Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 25 Sep 2021 13:58:16 +0200 Subject: [PATCH 128/278] Fix failing tests --- Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs index c1f5b5dfaf1..35959ee32a7 100644 --- a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs +++ b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs @@ -52,18 +52,8 @@ namespace Jellyfin.Server.Middleware return; } - // Unencode and re-parse querystring. - var unencodedKey = HttpUtility.UrlDecode(key); - - if (string.Equals(unencodedKey, key, StringComparison.Ordinal)) - { - // Don't do anything if it's not encoded. - _store = value; - return; - } - var pairs = new Dictionary(); - var queryString = unencodedKey.SpanSplit('&'); + var queryString = HttpUtility.UrlDecode(key).SpanSplit('&'); foreach (var pair in queryString) { From 4fc3de9b75e2b8ac9052271dca9aacf2bc6eed90 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 25 Sep 2021 05:21:48 -0700 Subject: [PATCH 129/278] Fix builds for dotnet6 (#6595) * Target net6.0 * Use new Enum.TryParse(ReadOnlySpan) overload * Replace RNGCryptoServiceProvider with RandomNumberGenerator * ci - target net6.0 (#6594) * Update deployment for dotnet6 * Use generic 6.0.x preview for CI * Update direct dotnet download links Co-authored-by: Bond_009 --- .ci/azure-pipelines-abi.yml | 3 ++- .ci/azure-pipelines-main.yml | 3 ++- .ci/azure-pipelines-package.yml | 3 ++- .ci/azure-pipelines-test.yml | 3 ++- Dockerfile | 2 +- Dockerfile.arm | 2 +- Dockerfile.arm64 | 2 +- debian/control | 2 +- deployment/Dockerfile.centos.amd64 | 10 +++++----- deployment/Dockerfile.debian.amd64 | 2 +- deployment/Dockerfile.debian.arm64 | 10 +++++----- deployment/Dockerfile.debian.armhf | 10 +++++----- deployment/Dockerfile.docker.amd64 | 4 +--- deployment/Dockerfile.docker.arm64 | 4 +--- deployment/Dockerfile.docker.armhf | 4 +--- deployment/Dockerfile.fedora.amd64 | 9 ++++++--- deployment/Dockerfile.linux.amd64 | 2 +- deployment/Dockerfile.linux.amd64-musl | 2 +- deployment/Dockerfile.linux.arm64 | 2 +- deployment/Dockerfile.linux.armhf | 2 +- deployment/Dockerfile.macos | 2 +- deployment/Dockerfile.portable | 2 +- deployment/Dockerfile.ubuntu.amd64 | 4 +--- deployment/Dockerfile.ubuntu.arm64 | 4 +--- deployment/Dockerfile.ubuntu.armhf | 4 +--- deployment/Dockerfile.windows.amd64 | 2 +- deployment/build.centos.amd64 | 19 +++++++++++++++++++ deployment/build.debian.amd64 | 4 ++-- deployment/build.debian.arm64 | 4 ++-- deployment/build.debian.armhf | 4 ++-- deployment/build.fedora.amd64 | 19 +++++++++++++++++++ deployment/build.ubuntu.amd64 | 4 ++-- deployment/build.ubuntu.arm64 | 4 ++-- deployment/build.ubuntu.armhf | 4 ++-- fedora/jellyfin.spec | 2 +- 35 files changed, 98 insertions(+), 65 deletions(-) diff --git a/.ci/azure-pipelines-abi.yml b/.ci/azure-pipelines-abi.yml index 46bc75608eb..31f861f63f7 100644 --- a/.ci/azure-pipelines-abi.yml +++ b/.ci/azure-pipelines-abi.yml @@ -7,7 +7,7 @@ parameters: default: "ubuntu-latest" - name: DotNetSdkVersion type: string - default: 6.0.100-rc.1.21458.32 + default: 6.0.x jobs: - job: CompatibilityCheck @@ -34,6 +34,7 @@ jobs: inputs: packageType: sdk version: ${{ parameters.DotNetSdkVersion }} + includePreviewVersions: true - task: DotNetCoreCLI@2 displayName: 'Install ABI CompatibilityChecker Tool' diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml index c07a93d44e1..1086d51d275 100644 --- a/.ci/azure-pipelines-main.yml +++ b/.ci/azure-pipelines-main.yml @@ -1,7 +1,7 @@ parameters: LinuxImage: 'ubuntu-latest' RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj' - DotNetSdkVersion: 6.0.100-rc.1.21458.32 + DotNetSdkVersion: 6.0.x jobs: - job: Build @@ -54,6 +54,7 @@ jobs: inputs: packageType: sdk version: ${{ parameters.DotNetSdkVersion }} + includePreviewVersions: true - task: DotNetCoreCLI@2 displayName: 'Publish Server' diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index b7b06e50551..adbb056ec8b 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -198,7 +198,8 @@ jobs: displayName: 'Use .NET 6.0 sdk' inputs: packageType: 'sdk' - version: '6.0.100-rc.1.21458.32' + version: '6.0.x' + includePreviewVersions: true - task: DotNetCoreCLI@2 displayName: 'Build Stable Nuget packages' diff --git a/.ci/azure-pipelines-test.yml b/.ci/azure-pipelines-test.yml index 99e305c740c..add261c365a 100644 --- a/.ci/azure-pipelines-test.yml +++ b/.ci/azure-pipelines-test.yml @@ -10,7 +10,7 @@ parameters: default: "tests/**/*Tests.csproj" - name: DotNetSdkVersion type: string - default: 6.0.100-rc.1.21458.32 + default: 6.0.x jobs: - job: Test @@ -41,6 +41,7 @@ jobs: inputs: packageType: sdk version: ${{ parameters.DotNetSdkVersion }} + includePreviewVersions: true - task: SonarCloudPrepare@1 displayName: 'Prepare analysis on SonarCloud' diff --git a/Dockerfile b/Dockerfile index 791a6113ede..73b5908b4ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ ##################################### # Requires binfm_misc registration # https://github.com/multiarch/qemu-user-static#binfmt_misc-register -ARG DOTNET_VERSION=5.0 +ARG DOTNET_VERSION=6.0 FROM node:lts-alpine as web-builder ARG JELLYFIN_WEB_VERSION=master diff --git a/Dockerfile.arm b/Dockerfile.arm index 8d4b548bcd7..edb8591c648 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -2,7 +2,7 @@ ##################################### # Requires binfm_misc registration # https://github.com/multiarch/qemu-user-static#binfmt_misc-register -ARG DOTNET_VERSION=5.0 +ARG DOTNET_VERSION=6.0 FROM node:lts-alpine as web-builder diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 835aa36a128..db1edcfe66f 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -2,7 +2,7 @@ ##################################### # Requires binfm_misc registration # https://github.com/multiarch/qemu-user-static#binfmt_misc-register -ARG DOTNET_VERSION=5.0 +ARG DOTNET_VERSION=6.0 FROM node:lts-alpine as web-builder diff --git a/debian/control b/debian/control index 51b20c670d5..da9aa94d4d2 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: misc Priority: optional Maintainer: Jellyfin Team Build-Depends: debhelper (>= 9), - dotnet-sdk-5.0, + dotnet-sdk-6.0, libc6-dev, libcurl4-openssl-dev, libfontconfig1-dev, diff --git a/deployment/Dockerfile.centos.amd64 b/deployment/Dockerfile.centos.amd64 index 326e995be63..178f94f7199 100644 --- a/deployment/Dockerfile.centos.amd64 +++ b/deployment/Dockerfile.centos.amd64 @@ -2,7 +2,6 @@ FROM centos:7 # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=5.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -11,12 +10,13 @@ ENV IS_DOCKER=YES # Prepare CentOS environment RUN yum update -yq \ && yum install -yq epel-release \ - && yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git + && yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget # Install DotNET SDK -RUN rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm \ - && rpmdev-setuptree \ - && yum install -yq dotnet-sdk-${SDK_VERSION} +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5fcb98bb-21e1-47a5-bb8e-bb25f41a3e52/04811d5d05b7e694f040d2a13c1aae4c/dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ + && mkdir -p dotnet-sdk \ + && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ + && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet # Create symlinks and directories RUN ln -sf ${SOURCE_DIR}/deployment/build.centos.amd64 /build.sh \ diff --git a/deployment/Dockerfile.debian.amd64 b/deployment/Dockerfile.debian.amd64 index 23b662526ed..daba0eb7d16 100644 --- a/deployment/Dockerfile.debian.amd64 +++ b/deployment/Dockerfile.debian.amd64 @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim +FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist diff --git a/deployment/Dockerfile.debian.arm64 b/deployment/Dockerfile.debian.arm64 index a33099031e7..db4e7f81784 100644 --- a/deployment/Dockerfile.debian.arm64 +++ b/deployment/Dockerfile.debian.arm64 @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim +FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist @@ -18,16 +18,16 @@ RUN apt-get update -yqq \ RUN dpkg --add-architecture arm64 \ && apt-get update -yqq \ && apt-get install -yqq --no-install-recommends cross-gcc-dev \ - && TARGET_LIST="arm64" cross-gcc-gensource 8 \ - && cd cross-gcc-packages-amd64/cross-gcc-8-arm64 \ + && TARGET_LIST="arm64" cross-gcc-gensource 9 \ + && cd cross-gcc-packages-amd64/cross-gcc-9-arm64 \ && apt-get install -yqq --no-install-recommends \ - gcc-8-source libstdc++-8-dev-arm64-cross \ + gcc-9-source libstdc++-9-dev-arm64-cross \ binutils-aarch64-linux-gnu bison flex libtool \ gdb sharutils netbase libmpc-dev libmpfr-dev libgmp-dev \ systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip \ libc6-dev:arm64 linux-libc-dev:arm64 libgcc1:arm64 \ libcurl4-openssl-dev:arm64 libfontconfig1-dev:arm64 \ - libfreetype6-dev:arm64 libssl-dev:arm64 liblttng-ust0:arm64 libstdc++-8-dev:arm64 + libfreetype6-dev:arm64 libssl-dev:arm64 liblttng-ust0:arm64 libstdc++-9-dev:arm64 # Link to build script RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.arm64 /build.sh diff --git a/deployment/Dockerfile.debian.armhf b/deployment/Dockerfile.debian.armhf index bc5e3543fa2..9b008e7fb04 100644 --- a/deployment/Dockerfile.debian.armhf +++ b/deployment/Dockerfile.debian.armhf @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim +FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist @@ -18,17 +18,17 @@ RUN apt-get update -yqq \ RUN dpkg --add-architecture armhf \ && apt-get update -yqq \ && apt-get install -yqq --no-install-recommends cross-gcc-dev \ - && TARGET_LIST="armhf" cross-gcc-gensource 8 \ - && cd cross-gcc-packages-amd64/cross-gcc-8-armhf \ + && TARGET_LIST="armhf" cross-gcc-gensource 9 \ + && cd cross-gcc-packages-amd64/cross-gcc-9-armhf \ && apt-get install -yqq --no-install-recommends\ - gcc-8-source libstdc++-8-dev-armhf-cross \ + gcc-9-source libstdc++-9-dev-armhf-cross \ binutils-aarch64-linux-gnu bison flex libtool gdb \ sharutils netbase libmpc-dev libmpfr-dev libgmp-dev \ systemtap-sdt-dev autogen expect chrpath zlib1g-dev \ zip binutils-arm-linux-gnueabihf libc6-dev:armhf \ linux-libc-dev:armhf libgcc1:armhf libcurl4-openssl-dev:armhf \ libfontconfig1-dev:armhf libfreetype6-dev:armhf libssl-dev:armhf \ - liblttng-ust0:armhf libstdc++-8-dev:armhf + liblttng-ust0:armhf libstdc++-9-dev:armhf # Link to build script RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.armhf /build.sh diff --git a/deployment/Dockerfile.docker.amd64 b/deployment/Dockerfile.docker.amd64 index 0b1a57014f4..b2bd40713db 100644 --- a/deployment/Dockerfile.docker.amd64 +++ b/deployment/Dockerfile.docker.amd64 @@ -1,6 +1,4 @@ -ARG DOTNET_VERSION=5.0 - -FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-buster-slim +FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim ARG SOURCE_DIR=/src ARG ARTIFACT_DIR=/jellyfin diff --git a/deployment/Dockerfile.docker.arm64 b/deployment/Dockerfile.docker.arm64 index 583f53ca09c..fc60f162465 100644 --- a/deployment/Dockerfile.docker.arm64 +++ b/deployment/Dockerfile.docker.arm64 @@ -1,6 +1,4 @@ -ARG DOTNET_VERSION=5.0 - -FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-buster-slim +FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim ARG SOURCE_DIR=/src ARG ARTIFACT_DIR=/jellyfin diff --git a/deployment/Dockerfile.docker.armhf b/deployment/Dockerfile.docker.armhf index 177c1171348..f5cc47d83e6 100644 --- a/deployment/Dockerfile.docker.armhf +++ b/deployment/Dockerfile.docker.armhf @@ -1,6 +1,4 @@ -ARG DOTNET_VERSION=5.0 - -FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-buster-slim +FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim ARG SOURCE_DIR=/src ARG ARTIFACT_DIR=/jellyfin diff --git a/deployment/Dockerfile.fedora.amd64 b/deployment/Dockerfile.fedora.amd64 index 590cde16776..f0f2977a406 100644 --- a/deployment/Dockerfile.fedora.amd64 +++ b/deployment/Dockerfile.fedora.amd64 @@ -2,7 +2,6 @@ FROM fedora:33 # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=5.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -10,10 +9,14 @@ ENV IS_DOCKER=YES # Prepare Fedora environment RUN dnf update -yq \ - && dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd + && dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget # Install DotNET SDK -RUN dnf install -yq dotnet-sdk-${SDK_VERSION} dotnet-runtime-${SDK_VERSION} +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5fcb98bb-21e1-47a5-bb8e-bb25f41a3e52/04811d5d05b7e694f040d2a13c1aae4c/dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ + && mkdir -p dotnet-sdk \ + && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ + && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet + # Create symlinks and directories RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora.amd64 /build.sh \ diff --git a/deployment/Dockerfile.linux.amd64 b/deployment/Dockerfile.linux.amd64 index 3c7e2b87f66..2c7e41cace0 100644 --- a/deployment/Dockerfile.linux.amd64 +++ b/deployment/Dockerfile.linux.amd64 @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim +FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist diff --git a/deployment/Dockerfile.linux.amd64-musl b/deployment/Dockerfile.linux.amd64-musl index 3cda9ad2350..e903cf1d320 100644 --- a/deployment/Dockerfile.linux.amd64-musl +++ b/deployment/Dockerfile.linux.amd64-musl @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim +FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist diff --git a/deployment/Dockerfile.linux.arm64 b/deployment/Dockerfile.linux.arm64 index ddf97cbd1ca..0dd3c5e4e81 100644 --- a/deployment/Dockerfile.linux.arm64 +++ b/deployment/Dockerfile.linux.arm64 @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim +FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist diff --git a/deployment/Dockerfile.linux.armhf b/deployment/Dockerfile.linux.armhf index 49e1c7bbf5a..16a8218e188 100644 --- a/deployment/Dockerfile.linux.armhf +++ b/deployment/Dockerfile.linux.armhf @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim +FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist diff --git a/deployment/Dockerfile.macos b/deployment/Dockerfile.macos index fad44ef8339..699ab2d408f 100644 --- a/deployment/Dockerfile.macos +++ b/deployment/Dockerfile.macos @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim +FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist diff --git a/deployment/Dockerfile.portable b/deployment/Dockerfile.portable index 90cc0717b70..b567d7bcea7 100644 --- a/deployment/Dockerfile.portable +++ b/deployment/Dockerfile.portable @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim +FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64 index 6b18e758360..fe1b4981bcf 100644 --- a/deployment/Dockerfile.ubuntu.amd64 +++ b/deployment/Dockerfile.ubuntu.amd64 @@ -2,7 +2,6 @@ FROM ubuntu:bionic # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=5.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -18,8 +17,7 @@ RUN apt-get update -yqq \ libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 # Install dotnet repository -# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5972698f-ba44-4664-9c50-bdc69ca70fb7/1cf7d94425d8dd4d5789dfa978d61475/dotnet-sdk-5.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5fcb98bb-21e1-47a5-bb8e-bb25f41a3e52/04811d5d05b7e694f040d2a13c1aae4c/dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64 index 50b116a6778..d984f5d8987 100644 --- a/deployment/Dockerfile.ubuntu.arm64 +++ b/deployment/Dockerfile.ubuntu.arm64 @@ -2,7 +2,6 @@ FROM ubuntu:bionic # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=5.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -17,8 +16,7 @@ RUN apt-get update -yqq \ mmv build-essential lsb-release # Install dotnet repository -# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5972698f-ba44-4664-9c50-bdc69ca70fb7/1cf7d94425d8dd4d5789dfa978d61475/dotnet-sdk-5.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5fcb98bb-21e1-47a5-bb8e-bb25f41a3e52/04811d5d05b7e694f040d2a13c1aae4c/dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf index 1b1e252f78c..c013e6797ea 100644 --- a/deployment/Dockerfile.ubuntu.armhf +++ b/deployment/Dockerfile.ubuntu.armhf @@ -2,7 +2,6 @@ FROM ubuntu:bionic # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=5.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -17,8 +16,7 @@ RUN apt-get update -yqq \ mmv build-essential lsb-release # Install dotnet repository -# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5972698f-ba44-4664-9c50-bdc69ca70fb7/1cf7d94425d8dd4d5789dfa978d61475/dotnet-sdk-5.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5fcb98bb-21e1-47a5-bb8e-bb25f41a3e52/04811d5d05b7e694f040d2a13c1aae4c/dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.windows.amd64 b/deployment/Dockerfile.windows.amd64 index acd0e18549c..b9543a7c919 100644 --- a/deployment/Dockerfile.windows.amd64 +++ b/deployment/Dockerfile.windows.amd64 @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim +FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG ARTIFACT_DIR=/dist diff --git a/deployment/build.centos.amd64 b/deployment/build.centos.amd64 index 69f0cadcfe4..bfdc6e591e2 100755 --- a/deployment/build.centos.amd64 +++ b/deployment/build.centos.amd64 @@ -8,6 +8,16 @@ set -o xtrace # Move to source directory pushd ${SOURCE_DIR} +if [[ ${IS_DOCKER} == YES ]]; then + # Remove BuildRequires for dotnet-sdk-6.0, since it's installed manually + pushd fedora + + cp -a jellyfin.spec /tmp/spec.orig + sed -i 's/BuildRequires: dotnet/# BuildRequires: dotnet/' jellyfin.spec + + popd +fi + # Modify changelog to unstable configuration if IS_UNSTABLE if [[ ${IS_UNSTABLE} == 'yes' ]]; then pushd fedora @@ -37,4 +47,13 @@ fi rm -f fedora/jellyfin*.tar.gz +if [[ ${IS_DOCKER} == YES ]]; then + pushd fedora + + cp -a /tmp/spec.orig jellyfin.spec + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + + popd +fi + popd diff --git a/deployment/build.debian.amd64 b/deployment/build.debian.amd64 index 145e28d871e..b2bbf9c29ef 100755 --- a/deployment/build.debian.amd64 +++ b/deployment/build.debian.amd64 @@ -9,9 +9,9 @@ set -o xtrace pushd ${SOURCE_DIR} if [[ ${IS_DOCKER} == YES ]]; then - # Remove build-dep for dotnet-sdk-5.0, since it's installed manually + # Remove build-dep for dotnet-sdk-6.0, since it's installed manually cp -a debian/control /tmp/control.orig - sed -i '/dotnet-sdk-5.0,/d' debian/control + sed -i '/dotnet-sdk-6.0,/d' debian/control fi # Modify changelog to unstable configuration if IS_UNSTABLE diff --git a/deployment/build.debian.arm64 b/deployment/build.debian.arm64 index 5699133a013..02f84471e6e 100755 --- a/deployment/build.debian.arm64 +++ b/deployment/build.debian.arm64 @@ -9,9 +9,9 @@ set -o xtrace pushd ${SOURCE_DIR} if [[ ${IS_DOCKER} == YES ]]; then - # Remove build-dep for dotnet-sdk-5.0, since it's installed manually + # Remove build-dep for dotnet-sdk-6.0, since it's installed manually cp -a debian/control /tmp/control.orig - sed -i '/dotnet-sdk-5.0,/d' debian/control + sed -i '/dotnet-sdk-6.0,/d' debian/control fi # Modify changelog to unstable configuration if IS_UNSTABLE diff --git a/deployment/build.debian.armhf b/deployment/build.debian.armhf index 20af2ddfbea..92779cb5948 100755 --- a/deployment/build.debian.armhf +++ b/deployment/build.debian.armhf @@ -9,9 +9,9 @@ set -o xtrace pushd ${SOURCE_DIR} if [[ ${IS_DOCKER} == YES ]]; then - # Remove build-dep for dotnet-sdk-5.0, since it's installed manually + # Remove build-dep for dotnet-sdk-6.0, since it's installed manually cp -a debian/control /tmp/control.orig - sed -i '/dotnet-sdk-5.0,/d' debian/control + sed -i '/dotnet-sdk-6.0,/d' debian/control fi # Modify changelog to unstable configuration if IS_UNSTABLE diff --git a/deployment/build.fedora.amd64 b/deployment/build.fedora.amd64 index 2c7bff50681..23c5ed86a21 100755 --- a/deployment/build.fedora.amd64 +++ b/deployment/build.fedora.amd64 @@ -8,6 +8,16 @@ set -o xtrace # Move to source directory pushd ${SOURCE_DIR} +if [[ ${IS_DOCKER} == YES ]]; then + # Remove BuildRequires for dotnet-sdk-6.0, since it's installed manually + pushd fedora + + cp -a jellyfin.spec /tmp/spec.orig + sed -i 's/BuildRequires: dotnet/# BuildRequires: dotnet/' jellyfin.spec + + popd +fi + # Modify changelog to unstable configuration if IS_UNSTABLE if [[ ${IS_UNSTABLE} == 'yes' ]]; then pushd fedora @@ -37,4 +47,13 @@ fi rm -f fedora/jellyfin*.tar.gz +if [[ ${IS_DOCKER} == YES ]]; then + pushd fedora + + cp -a /tmp/spec.orig jellyfin.spec + chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + + popd +fi + popd diff --git a/deployment/build.ubuntu.amd64 b/deployment/build.ubuntu.amd64 index 0c29286c020..c36978c9e35 100755 --- a/deployment/build.ubuntu.amd64 +++ b/deployment/build.ubuntu.amd64 @@ -9,9 +9,9 @@ set -o xtrace pushd ${SOURCE_DIR} if [[ ${IS_DOCKER} == YES ]]; then - # Remove build-dep for dotnet-sdk-5.0, since it's installed manually + # Remove build-dep for dotnet-sdk-6.0, since it's installed manually cp -a debian/control /tmp/control.orig - sed -i '/dotnet-sdk-5.0,/d' debian/control + sed -i '/dotnet-sdk-6.0,/d' debian/control fi # Modify changelog to unstable configuration if IS_UNSTABLE diff --git a/deployment/build.ubuntu.arm64 b/deployment/build.ubuntu.arm64 index 65d67f80f73..76d51e321f4 100755 --- a/deployment/build.ubuntu.arm64 +++ b/deployment/build.ubuntu.arm64 @@ -9,9 +9,9 @@ set -o xtrace pushd ${SOURCE_DIR} if [[ ${IS_DOCKER} == YES ]]; then - # Remove build-dep for dotnet-sdk-5.0, since it's installed manually + # Remove build-dep for dotnet-sdk-6.0, since it's installed manually cp -a debian/control /tmp/control.orig - sed -i '/dotnet-sdk-5.0,/d' debian/control + sed -i '/dotnet-sdk-6.0,/d' debian/control fi # Modify changelog to unstable configuration if IS_UNSTABLE diff --git a/deployment/build.ubuntu.armhf b/deployment/build.ubuntu.armhf index 370370abc14..0ff5ab06622 100755 --- a/deployment/build.ubuntu.armhf +++ b/deployment/build.ubuntu.armhf @@ -9,9 +9,9 @@ set -o xtrace pushd ${SOURCE_DIR} if [[ ${IS_DOCKER} == YES ]]; then - # Remove build-dep for dotnet-sdk-5.0, since it's installed manually + # Remove build-dep for dotnet-sdk-6.0, since it's installed manually cp -a debian/control /tmp/control.orig - sed -i '/dotnet-sdk-5.0,/d' debian/control + sed -i '/dotnet-sdk-6.0,/d' debian/control fi # Modify changelog to unstable configuration if IS_UNSTABLE diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec index 0d606f9f736..47dee7c13f3 100644 --- a/fedora/jellyfin.spec +++ b/fedora/jellyfin.spec @@ -27,7 +27,7 @@ BuildRequires: libcurl-devel, fontconfig-devel, freetype-devel, openssl-devel, # Requirements not packaged in main repos # COPR @dotnet-sig/dotnet or # https://packages.microsoft.com/rhel/7/prod/ -BuildRequires: dotnet-runtime-5.0, dotnet-sdk-5.0 +BuildRequires: dotnet-runtime-6.0, dotnet-sdk-6.0 Requires: %{name}-server = %{version}-%{release}, %{name}-web = %{version}-%{release} # Disable Automatic Dependency Processing AutoReqProv: no From d156d491135856990a6b92fad5f6402b5058c2e6 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 25 Sep 2021 06:40:19 -0600 Subject: [PATCH 130/278] Update codeql dotnet version --- .github/workflows/codeql-analysis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3e456f90936..e07d913b5a1 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -24,7 +24,9 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: '5.0.x' + dotnet-version: '6.0.x' + include-prerelease: true + - name: Initialize CodeQL uses: github/codeql-action/init@v1 with: From 984f17c64f0dac54d926124ae77866e1781799ba Mon Sep 17 00:00:00 2001 From: Ouail Ouazani Date: Fri, 24 Sep 2021 13:11:19 +0000 Subject: [PATCH 131/278] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/ --- Emby.Server.Implementations/Localization/Core/ar.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index be629c8a428..5a4a4d5a917 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -1,5 +1,5 @@ { - "Albums": "البومات", + "Albums": "ألبومات", "AppDeviceValues": "تطبيق: {0}, جهاز: {1}", "Application": "تطبيق", "Artists": "الفنانين", @@ -8,7 +8,7 @@ "CameraImageUploadedFrom": "صورة كاميرا جديدة تم رفعها من {0}", "Channels": "القنوات", "ChapterNameValue": "الفصل {0}", - "Collections": "مجموعات", + "Collections": "التجميعات", "DeviceOfflineWithName": "قُطِع الاتصال ب{0}", "DeviceOnlineWithName": "{0} متصل", "FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}", From d81c9a7a8cbab8967bfc66086c6f0f80bf7fc0c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Milinkovi=C4=87?= Date: Fri, 24 Sep 2021 11:31:45 +0000 Subject: [PATCH 132/278] Translated using Weblate (Croatian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hr/ --- Emby.Server.Implementations/Localization/Core/hr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json index 9eb80b83ba5..d7cda61da6d 100644 --- a/Emby.Server.Implementations/Localization/Core/hr.json +++ b/Emby.Server.Implementations/Localization/Core/hr.json @@ -15,7 +15,7 @@ "Favorites": "Favoriti", "Folders": "Mape", "Genres": "Žanrovi", - "HeaderAlbumArtists": "Izvođači na albumu", + "HeaderAlbumArtists": "Album od izvođača", "HeaderContinueWatching": "Nastavi gledati", "HeaderFavoriteAlbums": "Omiljeni albumi", "HeaderFavoriteArtists": "Omiljeni izvođači", From 5e8d58a6a6da61679fc896026869720ed3019bb9 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 25 Sep 2021 07:51:55 -0600 Subject: [PATCH 133/278] Update path for openapi spec --- .ci/azure-pipelines-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/azure-pipelines-test.yml b/.ci/azure-pipelines-test.yml index add261c365a..80a5732eeed 100644 --- a/.ci/azure-pipelines-test.yml +++ b/.ci/azure-pipelines-test.yml @@ -95,5 +95,5 @@ jobs: displayName: 'Publish OpenAPI Artifact' condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) inputs: - targetPath: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net5.0/openapi.json" + targetPath: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net6.0/openapi.json" artifactName: 'OpenAPI Spec' From 4643fd5dcbc6d1a4fbe973efc68d92ca71e3ab3b Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 25 Sep 2021 18:15:46 +0200 Subject: [PATCH 134/278] Address comments --- Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs | 10 +++++++--- .../Subtitles/SubtitleEditParser.cs | 2 +- .../EncodedQueryStringTest.cs | 1 + .../UrlDecodeQueryFeatureTests.cs | 3 --- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs index 35959ee32a7..b5f515cdab4 100644 --- a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs +++ b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs @@ -52,10 +52,14 @@ namespace Jellyfin.Server.Middleware return; } - var pairs = new Dictionary(); - var queryString = HttpUtility.UrlDecode(key).SpanSplit('&'); + if (!key.Contains('=')) + { + _store = value; + return; + } - foreach (var pair in queryString) + var pairs = new Dictionary(); + foreach (var pair in key.SpanSplit('&')) { var i = pair.IndexOf('='); if (i == -1) diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs index 20e4be78026..52c1b64677e 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles subRip.LoadSubtitle(subtitle, lines, "untitled"); if (subRip.ErrorCount > 0) { - _logger.LogError("{ErrorCount} errors encountered while parsing subtitle.", subRip.ErrorCount); + _logger.LogError("{ErrorCount} errors encountered while parsing subtitle", subRip.ErrorCount); } var trackInfo = new SubtitleTrackInfo(); diff --git a/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs b/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs index 732b4f050dc..2361e4aa4e6 100644 --- a/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs +++ b/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs @@ -21,6 +21,7 @@ namespace Jellyfin.Server.Integration.Tests [InlineData("a=1", "a=1")] // won't be processed as it has a value [InlineData("a%3D1%26b%3D2%26c%3D3", "a=1&b=2&c=3")] // will be processed. [InlineData("a=b&a=c", "a=b")] + [InlineData("a%3D1", "a=1")] [InlineData("a%3Db%26a%3Dc", "a=b")] public async Task Ensure_Decoding_Of_Urls_Is_Working(string sourceUrl, string unencodedUrl) { diff --git a/tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs b/tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs index 419afb2dc40..d15c9d6f545 100644 --- a/tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs +++ b/tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs @@ -12,9 +12,6 @@ namespace Jellyfin.Server.Tests { [Theory] [InlineData("e0a72cb2a2c7", "e0a72cb2a2c7")] // isn't encoded - [InlineData("random+test", "random test")] // encoded - [InlineData("random%20test", "random test")] // encoded - [InlineData("++", " ")] // encoded public static void EmptyValueTest(string query, string key) { var dict = new Dictionary From 65665de660d19fa6b02d9d18c3e069d0fa5e5bdd Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 25 Sep 2021 19:36:29 +0200 Subject: [PATCH 135/278] Fix user DB migration --- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index d9524645a1d..9b2d603c7fa 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -1,7 +1,6 @@ using System; using System.IO; using Emby.Server.Implementations.Data; -using Emby.Server.Implementations.Serialization; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Extensions.Json; @@ -10,6 +9,7 @@ using Jellyfin.Server.Implementations.Users; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; using Microsoft.Extensions.Logging; using SQLitePCL.pretty; @@ -27,7 +27,7 @@ namespace Jellyfin.Server.Migrations.Routines private readonly ILogger _logger; private readonly IServerApplicationPaths _paths; private readonly JellyfinDbProvider _provider; - private readonly MyXmlSerializer _xmlSerializer; + private readonly IXmlSerializer _xmlSerializer; /// /// Initializes a new instance of the class. @@ -40,7 +40,7 @@ namespace Jellyfin.Server.Migrations.Routines ILogger logger, IServerApplicationPaths paths, JellyfinDbProvider provider, - MyXmlSerializer xmlSerializer) + IXmlSerializer xmlSerializer) { _logger = logger; _paths = paths; From f31224fa8f1dccb730703c048c26e14b5d14fa55 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 25 Sep 2021 19:44:40 +0200 Subject: [PATCH 136/278] Remove sync FileStream hack --- Emby.Dlna/DlnaManager.cs | 2 +- Emby.Drawing/ImageProcessor.cs | 2 +- .../LiveTv/EmbyTV/DirectRecorder.cs | 4 ++-- .../LiveTv/EmbyTV/EncodedRecorder.cs | 2 +- .../LiveTv/Listings/XmlTvListingsProvider.cs | 2 +- .../LiveTv/TunerHosts/LiveStream.cs | 6 ++--- .../LiveTv/TunerHosts/SharedHttpStream.cs | 2 +- .../Controllers/RemoteImageController.cs | 2 +- Jellyfin.Api/Controllers/SystemController.cs | 2 +- Jellyfin.Api/Helpers/HlsHelpers.cs | 2 +- Jellyfin.Api/Helpers/ProgressiveFileStream.cs | 24 ++----------------- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 2 +- ...linkFollowingPhysicalFileResultExecutor.cs | 2 +- Jellyfin.Server/Program.cs | 2 +- .../Subtitles/SubtitleEncoder.cs | 2 +- MediaBrowser.Model/IO/AsyncFile.cs | 11 ++------- MediaBrowser.Providers/Manager/ImageSaver.cs | 2 +- .../Manager/ItemImageProvider.cs | 2 +- .../Plugins/AudioDb/AudioDbAlbumProvider.cs | 2 +- .../Plugins/AudioDb/AudioDbArtistProvider.cs | 2 +- .../Plugins/Omdb/OmdbProvider.cs | 4 ++-- .../Studios/StudiosImageProvider.cs | 2 +- .../Subtitles/SubtitleManager.cs | 2 +- 23 files changed, 29 insertions(+), 56 deletions(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 305e43a3ca3..8fe9d484e71 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -366,7 +366,7 @@ namespace Emby.Dlna Directory.CreateDirectory(systemProfilesPath); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO)) + using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) { await stream.CopyToAsync(fileStream).ConfigureAwait(false); } diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 0ad8bca3148..9b130fdfd8f 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -102,7 +102,7 @@ namespace Emby.Drawing { var file = await ProcessImage(options).ConfigureAwait(false); - using (var fileStream = new FileStream(file.Item1, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO)) + using (var fileStream = new FileStream(file.Item1, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) { await fileStream.CopyToAsync(toStream).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index f6e0111b6d0..41381d55bdc 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile))); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO)) + using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) { onStarted(); @@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile))); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.CopyToBufferSize, AsyncFile.UseAsyncIO); + await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.CopyToBufferSize, FileOptions.Asynchronous); onStarted(); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index a943efcc703..3633fa3e106 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV Directory.CreateDirectory(Path.GetDirectoryName(logFilePath)); // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - _logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); + _logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); await JsonSerializer.SerializeAsync(_logFileStream, mediaSource, _jsonOptions, cancellationToken).ConfigureAwait(false); await _logFileStream.WriteAsync(Encoding.UTF8.GetBytes(Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine), cancellationToken).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index cb9801c1786..2e51ac80717 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -82,7 +82,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(path, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - await using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.CopyToBufferSize, AsyncFile.UseAsyncIO)) + await using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.CopyToBufferSize, FileOptions.Asynchronous)) { await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 817b1f80466..20a9a705b87 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -97,7 +97,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public Stream GetStream() { - var stream = GetInputStream(TempFilePath, AsyncFile.UseAsyncIO); + var stream = GetInputStream(TempFilePath); bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10; if (seekFile) { @@ -107,14 +107,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return stream; } - protected FileStream GetInputStream(string path, bool allowAsyncFileRead) + protected FileStream GetInputStream(string path) => new FileStream( path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, - allowAsyncFileRead ? FileOptions.SequentialScan | FileOptions.Asynchronous : FileOptions.SequentialScan); + FileOptions.SequentialScan | FileOptions.Asynchronous); protected async Task DeleteTempFiles(string path, int retryCount = 0) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 84a878e5178..3b69e55b01c 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -118,7 +118,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Logger.LogInformation("Beginning {StreamType} stream to {FilePath}", GetType().Name, TempFilePath); using var message = response; await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); + await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); await StreamHelper.CopyToAsync( stream, fileStream, diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs index bcb2b50c7a3..8a33b12f4c7 100644 --- a/Jellyfin.Api/Controllers/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/RemoteImageController.cs @@ -206,7 +206,7 @@ namespace Jellyfin.Api.Controllers var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid."); Directory.CreateDirectory(fullCacheDirectory); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); + await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); await response.Content.CopyToAsync(fileStream).ConfigureAwait(false); var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath)); diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index e6584f0fe62..741bdfee978 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -201,7 +201,7 @@ namespace Jellyfin.Api.Controllers // For older files, assume fully static var fileShare = file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1) ? FileShare.Read : FileShare.ReadWrite; - FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); + FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); return File(stream, "text/plain; charset=utf-8"); } diff --git a/Jellyfin.Api/Helpers/HlsHelpers.cs b/Jellyfin.Api/Helpers/HlsHelpers.cs index f36769dc2a2..4567621470b 100644 --- a/Jellyfin.Api/Helpers/HlsHelpers.cs +++ b/Jellyfin.Api/Helpers/HlsHelpers.cs @@ -38,7 +38,7 @@ namespace Jellyfin.Api.Helpers FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, - (AsyncFile.UseAsyncIO ? FileOptions.Asynchronous : FileOptions.None) | FileOptions.SequentialScan); + FileOptions.Asynchronous | FileOptions.SequentialScan); await using (fileStream.ConfigureAwait(false)) { using var reader = new StreamReader(fileStream); diff --git a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs index c5701835145..61e18220a1b 100644 --- a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs +++ b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs @@ -17,7 +17,6 @@ namespace Jellyfin.Api.Helpers private readonly TranscodingJobDto? _job; private readonly TranscodingJobHelper? _transcodingJobHelper; private readonly int _timeoutMs; - private readonly bool _allowAsyncFileRead; private int _bytesWritten; private bool _disposed; @@ -34,17 +33,7 @@ namespace Jellyfin.Api.Helpers _transcodingJobHelper = transcodingJobHelper; _timeoutMs = timeoutMs; - var fileOptions = FileOptions.SequentialScan; - _allowAsyncFileRead = false; - - // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - if (AsyncFile.UseAsyncIO) - { - fileOptions |= FileOptions.Asynchronous; - _allowAsyncFileRead = true; - } - - _stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, fileOptions); + _stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous | FileOptions.SequentialScan); } /// @@ -57,7 +46,6 @@ namespace Jellyfin.Api.Helpers _job = null; _transcodingJobHelper = null; _timeoutMs = timeoutMs; - _allowAsyncFileRead = AsyncFile.UseAsyncIO; _stream = stream; } @@ -103,15 +91,7 @@ namespace Jellyfin.Api.Helpers while (remainingBytesToRead > 0) { cancellationToken.ThrowIfCancellationRequested(); - int bytesRead; - if (_allowAsyncFileRead) - { - bytesRead = await _stream.ReadAsync(buffer, newOffset, remainingBytesToRead, cancellationToken).ConfigureAwait(false); - } - else - { - bytesRead = _stream.Read(buffer, newOffset, remainingBytesToRead); - } + int bytesRead = await _stream.ReadAsync(buffer, newOffset, remainingBytesToRead, cancellationToken).ConfigureAwait(false); remainingBytesToRead -= bytesRead; newOffset += bytesRead; diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 4e1e98df0d8..14f287aef68 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -557,7 +557,7 @@ namespace Jellyfin.Api.Helpers $"{logFilePrefix}{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{state.Request.MediaSourceId}_{Guid.NewGuid().ToString()[..8]}.log"); // FFmpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); + Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(request.Path + Environment.NewLine + Environment.NewLine + JsonSerializer.Serialize(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); await logStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false); diff --git a/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs b/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs index 4abd5b36d1d..f3fbab77e49 100644 --- a/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs +++ b/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs @@ -132,7 +132,7 @@ namespace Jellyfin.Server.Infrastructure FileAccess.Read, FileShare.ReadWrite, bufferSize: BufferSize, - options: (AsyncFile.UseAsyncIO ? FileOptions.Asynchronous : FileOptions.None) | FileOptions.SequentialScan); + options: FileOptions.Asynchronous | FileOptions.SequentialScan); fileStream.Seek(offset, SeekOrigin.Begin); await StreamCopyOperation diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 1300ce3b675..f36675b9550 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -547,7 +547,7 @@ namespace Jellyfin.Server ?? throw new InvalidOperationException($"Invalid resource path: '{ResourcePath}'"); // Copy the resource contents to the expected file path for the config file - await using Stream dst = new FileStream(configPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); + await using Stream dst = new FileStream(configPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); await resource.CopyToAsync(dst).ConfigureAwait(false); } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index f8451e92c39..022cdbe9d35 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -680,7 +680,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles if (!string.Equals(text, newText, StringComparison.Ordinal)) { // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO)) + using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) using (var writer = new StreamWriter(fileStream, encoding)) { await writer.WriteAsync(newText.AsMemory(), cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Model/IO/AsyncFile.cs b/MediaBrowser.Model/IO/AsyncFile.cs index b888a416395..f38ed9ae3d4 100644 --- a/MediaBrowser.Model/IO/AsyncFile.cs +++ b/MediaBrowser.Model/IO/AsyncFile.cs @@ -8,20 +8,13 @@ namespace MediaBrowser.Model.IO /// public static class AsyncFile { - /// - /// Gets a value indicating whether we should use async IO on this platform. - /// . - /// - /// Returns false on Windows; otherwise true. - public static bool UseAsyncIO => !OperatingSystem.IsWindows(); - /// /// Opens an existing file for reading. /// /// The file to be opened for reading. /// A read-only on the specified path. public static FileStream OpenRead(string path) - => new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, UseAsyncIO); + => new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); /// /// Opens an existing file for writing. @@ -29,6 +22,6 @@ namespace MediaBrowser.Model.IO /// The file to be opened for writing. /// An unshared object on the specified path with Write access. public static FileStream OpenWrite(string path) - => new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, UseAsyncIO); + => new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); } } diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 6c14c8de1e3..7d259a9d36a 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -264,7 +264,7 @@ namespace MediaBrowser.Providers.Manager _fileSystem.SetAttributes(path, false, false); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO)) + await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) { await source.CopyToAsync(fs, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 81529e98469..73bca5aa59c 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -163,7 +163,7 @@ namespace MediaBrowser.Providers.Manager { var mimeType = MimeTypes.GetMimeType(response.Path); - var stream = new FileStream(response.Path, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); + var stream = new FileStream(response.Path, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); await _providerManager.SaveImage(item, stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs index c1226febfcd..3e0b0014eb8 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs @@ -173,7 +173,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); + await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs index 8572b3413da..e0b2f9c5888 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs @@ -155,7 +155,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb Directory.CreateDirectory(Path.GetDirectoryName(path)); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); + await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index 1dea3dece13..479ae0f3918 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -295,7 +295,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb imdbParam)); var rootObject = await GetDeserializedOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false); - await using FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); + await using FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); await JsonSerializer.SerializeAsync(jsonFileStream, rootObject, _jsonOptions, cancellationToken).ConfigureAwait(false); return path; @@ -335,7 +335,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb seasonId)); var rootObject = await GetDeserializedOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false); - await using FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); + await using FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); await JsonSerializer.SerializeAsync(jsonFileStream, rootObject, _jsonOptions, cancellationToken).ConfigureAwait(false); return path; diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs index 7a057c065fa..7b2454efc99 100644 --- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs @@ -146,7 +146,7 @@ namespace MediaBrowser.Providers.Studios Directory.CreateDirectory(Path.GetDirectoryName(file)); await using var response = await httpClient.GetStreamAsync(url, cancellationToken).ConfigureAwait(false); - await using var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO); + await using var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); await response.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index d6c346ba12b..772e617abb3 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -245,7 +245,7 @@ namespace MediaBrowser.Providers.Subtitles Directory.CreateDirectory(Path.GetDirectoryName(savePath)); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, FileStreamBufferSize, AsyncFile.UseAsyncIO); + using var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, FileStreamBufferSize, FileOptions.Asynchronous); await stream.CopyToAsync(fs).ConfigureAwait(false); return; From a4eede29abf1cf380c959835e59b7062ead1a654 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 25 Sep 2021 20:17:12 +0200 Subject: [PATCH 137/278] Use RandomAccess instead of a FileStream where it makes sense --- Emby.Server.Implementations/IO/ManagedFileSystem.cs | 4 ++-- .../SymlinkFollowingPhysicalFileResultExecutor.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 1bc229b0cab..77da46cd6d7 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -246,9 +246,9 @@ namespace Emby.Server.Implementations.IO { try { - using (Stream thisFileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read, 1)) + using (var fileHandle = File.OpenHandle(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { - result.Length = thisFileStream.Length; + result.Length = RandomAccess.GetLength(fileHandle); } } catch (FileNotFoundException ex) diff --git a/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs b/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs index 4abd5b36d1d..2296ac5c8a6 100644 --- a/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs +++ b/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs @@ -55,8 +55,8 @@ namespace Jellyfin.Server.Infrastructure // This may or may not be fixed in .NET 6, but looks like it will not https://github.com/dotnet/aspnetcore/issues/34371 if ((fileInfo.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint) { - using Stream thisFileStream = File.OpenRead(path); - length = thisFileStream.Length; + using var fileHandle = File.OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + length = RandomAccess.GetLength(fileHandle); } return new FileMetadata From 79642af3b8a9e2f001ae07902090b68e677bb949 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 25 Sep 2021 20:32:53 +0200 Subject: [PATCH 138/278] Fix some warnings --- .../Library/LiveStreamHelper.cs | 2 +- .../LiveTv/TunerHosts/BaseTunerHost.cs | 18 +++++++++-------- .../HdHomerun/HdHomerunUdpStream.cs | 3 ++- .../LiveTv/TunerHosts/LiveStream.cs | 18 ++++++++--------- .../Playlists/ManualPlaylistsFolder.cs | 18 ++++++++--------- .../Plugins/PluginManager.cs | 20 +++++++++---------- 6 files changed, 41 insertions(+), 38 deletions(-) diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index 16b45161fd0..83acd8e9f2b 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -10,9 +10,9 @@ using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using Jellyfin.Extensions.Json; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 096b7f04545..2b82f246238 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -23,10 +23,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { public abstract class BaseTunerHost { - protected readonly IServerConfigurationManager Config; - protected readonly ILogger Logger; - protected readonly IFileSystem FileSystem; - private readonly IMemoryCache _memoryCache; protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IFileSystem fileSystem, IMemoryCache memoryCache) @@ -37,12 +33,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts FileSystem = fileSystem; } + protected IServerConfigurationManager Config { get; } + + protected ILogger Logger { get; } + + protected IFileSystem FileSystem { get; } + public virtual bool IsSupported => true; - protected abstract Task> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken); - public abstract string Type { get; } + protected virtual string ChannelIdPrefix => Type + "_"; + + protected abstract Task> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken); + public async Task> GetChannels(TunerHostInfo tuner, bool enableCache, CancellationToken cancellationToken) { var key = tuner.Id; @@ -217,8 +221,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts throw new LiveTvConflictException(); } - protected virtual string ChannelIdPrefix => Type + "_"; - protected virtual bool IsValidChannelId(string channelId) { if (string.IsNullOrEmpty(channelId)) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 6a2e7f69987..31445e1ecca 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -101,7 +101,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - if (localAddress.IsIPv4MappedToIPv6) { + if (localAddress.IsIPv4MappedToIPv6) + { localAddress = localAddress.MapToIPv4(); } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 817b1f80466..f024e49344b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -20,14 +20,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { private readonly IConfigurationManager _configurationManager; - protected readonly IFileSystem FileSystem; - - protected readonly IStreamHelper StreamHelper; - - protected string TempFilePath; - protected readonly ILogger Logger; - protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource(); - public LiveStream( MediaSourceInfo mediaSource, TunerHostInfo tuner, @@ -55,7 +47,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts SetTempFilePath("ts"); } - protected virtual int EmptyReadLimit => 1000; + protected IFileSystem FileSystem { get; } + + protected IStreamHelper StreamHelper { get; } + + protected ILogger Logger { get; } + + protected CancellationTokenSource LiveStreamCancellationTokenSource { get; } = new CancellationTokenSource(); + + protected string TempFilePath { get; set; } public MediaSourceInfo OriginalMediaSource { get; set; } diff --git a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index 4160f3a500d..8b1cee89d13 100644 --- a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -17,6 +17,15 @@ namespace Emby.Server.Implementations.Playlists Name = "Playlists"; } + [JsonIgnore] + public override bool IsHidden => true; + + [JsonIgnore] + public override bool SupportsInheritedParentImages => false; + + [JsonIgnore] + public override string CollectionType => MediaBrowser.Model.Entities.CollectionType.Playlists; + public override bool IsVisible(User user) { return base.IsVisible(user) && GetChildren(user, true).Any(); @@ -27,15 +36,6 @@ namespace Emby.Server.Implementations.Playlists return base.GetEligibleChildrenForRecursiveChildren(user).OfType(); } - [JsonIgnore] - public override bool IsHidden => true; - - [JsonIgnore] - public override bool SupportsInheritedParentImages => false; - - [JsonIgnore] - public override string CollectionType => MediaBrowser.Model.Entities.CollectionType.Playlists; - protected override QueryResult GetItemsInternal(InternalItemsQuery query) { if (query.User == null) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index b8e1dc2c05b..d52c0b2a140 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -8,10 +8,10 @@ using System.Reflection; using System.Text; using System.Text.Json; using System.Threading.Tasks; -using MediaBrowser.Common; -using MediaBrowser.Common.Extensions; using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json.Converters; +using MediaBrowser.Common; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Configuration; @@ -39,14 +39,6 @@ namespace Emby.Server.Implementations.Plugins private IHttpClientFactory? _httpClientFactory; - private IHttpClientFactory HttpClientFactory - { - get - { - return _httpClientFactory ?? (_httpClientFactory = _appHost.Resolve()); - } - } - /// /// Initializes a new instance of the class. /// @@ -86,6 +78,14 @@ namespace Emby.Server.Implementations.Plugins _plugins = Directory.Exists(_pluginsPath) ? DiscoverPlugins().ToList() : new List(); } + private IHttpClientFactory HttpClientFactory + { + get + { + return _httpClientFactory ??= _appHost.Resolve(); + } + } + /// /// Gets the Plugins. /// From 9234e5bf80194e45acac25c60cb76f401bffaf96 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 26 Sep 2021 08:14:36 -0600 Subject: [PATCH 139/278] Remove all instances of en-US culture --- Emby.Dlna/Didl/DidlBuilder.cs | 28 ++++++------- Emby.Dlna/Eventing/DlnaEventManager.cs | 8 ++-- Emby.Dlna/PlayTo/Device.cs | 12 +++--- Emby.Dlna/PlayTo/PlayToController.cs | 8 ++-- Emby.Dlna/PlayTo/SsdpHttpClient.cs | 8 ++-- Emby.Dlna/Server/DescriptionXmlBuilder.cs | 5 +-- .../MediaEncoder/EncodingManager.cs | 3 +- Jellyfin.Api/Controllers/ImageController.cs | 2 +- .../Controllers/SubtitleController.cs | 2 +- MediaBrowser.Controller/Entities/Year.cs | 4 +- .../MediaEncoding/EncodingHelper.cs | 42 +++++++++---------- .../MediaEncoding/JobLogger.cs | 11 +++-- .../Images/LocalImageProvider.cs | 4 +- .../Parsers/BaseItemXmlParser.cs | 8 ++-- .../Savers/BaseXmlSaver.cs | 12 +++--- .../Encoder/MediaEncoder.cs | 11 ++--- .../Probing/ProbeResultNormalizer.cs | 27 ++++++------ .../Globalization/ILocalizationManager.cs | 4 +- MediaBrowser.Providers/Manager/ImageSaver.cs | 18 ++++---- .../Plugins/Omdb/OmdbProvider.cs | 13 +++--- .../Parsers/BaseNfoParser.cs | 10 ++--- .../Parsers/EpisodeNfoParser.cs | 10 ++--- .../Savers/EpisodeNfoSaver.cs | 18 ++++---- 23 files changed, 116 insertions(+), 152 deletions(-) diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index c0007849972..0a84f30c4cf 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -41,8 +41,6 @@ namespace Emby.Dlna.Didl private const string NsUpnp = "urn:schemas-upnp-org:metadata-1-0/upnp/"; private const string NsDlna = "urn:schemas-dlna-org:metadata-1-0/"; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private readonly DeviceProfile _profile; private readonly IImageProcessor _imageProcessor; private readonly string _serverAddress; @@ -317,7 +315,7 @@ namespace Emby.Dlna.Didl if (mediaSource.RunTimeTicks.HasValue) { - writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture)); + writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", CultureInfo.InvariantCulture)); } if (filter.Contains("res@size")) @@ -328,7 +326,7 @@ namespace Emby.Dlna.Didl if (size.HasValue) { - writer.WriteAttributeString("size", size.Value.ToString(_usCulture)); + writer.WriteAttributeString("size", size.Value.ToString(CultureInfo.InvariantCulture)); } } } @@ -342,7 +340,7 @@ namespace Emby.Dlna.Didl if (targetChannels.HasValue) { - writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(_usCulture)); + writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(CultureInfo.InvariantCulture)); } if (filter.Contains("res@resolution")) @@ -361,12 +359,12 @@ namespace Emby.Dlna.Didl if (targetSampleRate.HasValue) { - writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(_usCulture)); + writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(CultureInfo.InvariantCulture)); } if (totalBitrate.HasValue) { - writer.WriteAttributeString("bitrate", totalBitrate.Value.ToString(_usCulture)); + writer.WriteAttributeString("bitrate", totalBitrate.Value.ToString(CultureInfo.InvariantCulture)); } var mediaProfile = _profile.GetVideoMediaProfile( @@ -552,7 +550,7 @@ namespace Emby.Dlna.Didl if (mediaSource.RunTimeTicks.HasValue) { - writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture)); + writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", CultureInfo.InvariantCulture)); } if (filter.Contains("res@size")) @@ -563,7 +561,7 @@ namespace Emby.Dlna.Didl if (size.HasValue) { - writer.WriteAttributeString("size", size.Value.ToString(_usCulture)); + writer.WriteAttributeString("size", size.Value.ToString(CultureInfo.InvariantCulture)); } } } @@ -575,17 +573,17 @@ namespace Emby.Dlna.Didl if (targetChannels.HasValue) { - writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(_usCulture)); + writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(CultureInfo.InvariantCulture)); } if (targetSampleRate.HasValue) { - writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(_usCulture)); + writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(CultureInfo.InvariantCulture)); } if (targetAudioBitrate.HasValue) { - writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(_usCulture)); + writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(CultureInfo.InvariantCulture)); } var mediaProfile = _profile.GetAudioMediaProfile( @@ -639,7 +637,7 @@ namespace Emby.Dlna.Didl writer.WriteAttributeString("restricted", "1"); writer.WriteAttributeString("searchable", "1"); - writer.WriteAttributeString("childCount", childCount.ToString(_usCulture)); + writer.WriteAttributeString("childCount", childCount.ToString(CultureInfo.InvariantCulture)); var clientId = GetClientId(folder, stubType); @@ -931,11 +929,11 @@ namespace Emby.Dlna.Didl if (item.IndexNumber.HasValue) { - AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NsUpnp); + AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture), NsUpnp); if (item is Episode) { - AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NsUpnp); + AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture), NsUpnp); } } } diff --git a/Emby.Dlna/Eventing/DlnaEventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs index b39bd5ce9b5..d17e238715f 100644 --- a/Emby.Dlna/Eventing/DlnaEventManager.cs +++ b/Emby.Dlna/Eventing/DlnaEventManager.cs @@ -26,8 +26,6 @@ namespace Emby.Dlna.Eventing private readonly ILogger _logger; private readonly IHttpClientFactory _httpClientFactory; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public DlnaEventManager(ILogger logger, IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; @@ -83,7 +81,7 @@ namespace Emby.Dlna.Eventing if (!string.IsNullOrEmpty(header)) { // Starts with SECOND- - if (int.TryParse(header.AsSpan().RightPart('-'), NumberStyles.Integer, _usCulture, out var val)) + if (int.TryParse(header.AsSpan().RightPart('-'), NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) { return val; } @@ -106,7 +104,7 @@ namespace Emby.Dlna.Eventing var response = new EventSubscriptionResponse(string.Empty, "text/plain"); response.Headers["SID"] = subscriptionId; - response.Headers["TIMEOUT"] = string.IsNullOrEmpty(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(_usCulture)) : requestedTimeoutString; + response.Headers["TIMEOUT"] = string.IsNullOrEmpty(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(CultureInfo.InvariantCulture)) : requestedTimeoutString; return response; } @@ -163,7 +161,7 @@ namespace Emby.Dlna.Eventing options.Headers.TryAddWithoutValidation("NT", subscription.NotificationType); options.Headers.TryAddWithoutValidation("NTS", "upnp:propchange"); options.Headers.TryAddWithoutValidation("SID", subscription.Id); - options.Headers.TryAddWithoutValidation("SEQ", subscription.TriggerCount.ToString(_usCulture)); + options.Headers.TryAddWithoutValidation("SEQ", subscription.TriggerCount.ToString(CultureInfo.InvariantCulture)); try { diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 11fcd81cfff..0b228800031 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -20,8 +20,6 @@ namespace Emby.Dlna.PlayTo { public class Device : IDisposable { - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; @@ -640,7 +638,7 @@ namespace Emby.Dlna.PlayTo return; } - Volume = int.Parse(volumeValue, UsCulture); + Volume = int.Parse(volumeValue, CultureInfo.InvariantCulture); if (Volume > 0) { @@ -842,7 +840,7 @@ namespace Emby.Dlna.PlayTo if (!string.IsNullOrWhiteSpace(duration) && !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) { - Duration = TimeSpan.Parse(duration, UsCulture); + Duration = TimeSpan.Parse(duration, CultureInfo.InvariantCulture); } else { @@ -854,7 +852,7 @@ namespace Emby.Dlna.PlayTo if (!string.IsNullOrWhiteSpace(position) && !string.Equals(position, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) { - Position = TimeSpan.Parse(position, UsCulture); + Position = TimeSpan.Parse(position, CultureInfo.InvariantCulture); } var track = result.Document.Descendants("TrackMetaData").FirstOrDefault(); @@ -1194,8 +1192,8 @@ namespace Emby.Dlna.PlayTo var depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth")); var url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url")); - var widthValue = int.Parse(width, NumberStyles.Integer, UsCulture); - var heightValue = int.Parse(height, NumberStyles.Integer, UsCulture); + var widthValue = int.Parse(width, NumberStyles.Integer, CultureInfo.InvariantCulture); + var heightValue = int.Parse(height, NumberStyles.Integer, CultureInfo.InvariantCulture); return new DeviceIcon { diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 0e49fd2c02e..f25d8017efb 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -30,8 +30,6 @@ namespace Emby.Dlna.PlayTo { public class PlayToController : ISessionController, IDisposable { - private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US")); - private readonly SessionInfo _session; private readonly ISessionManager _sessionManager; private readonly ILibraryManager _libraryManager; @@ -716,7 +714,7 @@ namespace Emby.Dlna.PlayTo case GeneralCommandType.SetAudioStreamIndex: if (command.Arguments.TryGetValue("Index", out string index)) { - if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val)) + if (int.TryParse(index, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) { return SetAudioStreamIndex(val); } @@ -728,7 +726,7 @@ namespace Emby.Dlna.PlayTo case GeneralCommandType.SetSubtitleStreamIndex: if (command.Arguments.TryGetValue("Index", out index)) { - if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val)) + if (int.TryParse(index, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) { return SetSubtitleStreamIndex(val); } @@ -740,7 +738,7 @@ namespace Emby.Dlna.PlayTo case GeneralCommandType.SetVolume: if (command.Arguments.TryGetValue("Volume", out string vol)) { - if (int.TryParse(vol, NumberStyles.Integer, _usCulture, out var volume)) + if (int.TryParse(vol, NumberStyles.Integer, CultureInfo.InvariantCulture, out var volume)) { return _device.SetVolume(volume, cancellationToken); } diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index 4b92fbff439..cade7b4c2c2 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -20,8 +20,6 @@ namespace Emby.Dlna.PlayTo private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50"; private const string FriendlyName = "Jellyfin"; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private readonly IHttpClientFactory _httpClientFactory; public SsdpHttpClient(IHttpClientFactory httpClientFactory) @@ -80,10 +78,10 @@ namespace Emby.Dlna.PlayTo { using var options = new HttpRequestMessage(new HttpMethod("SUBSCRIBE"), url); options.Headers.UserAgent.ParseAdd(USERAGENT); - options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(_usCulture)); - options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(_usCulture) + ">"); + options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(CultureInfo.InvariantCulture)); + options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(CultureInfo.InvariantCulture) + ">"); options.Headers.TryAddWithoutValidation("NT", "upnp:event"); - options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(_usCulture)); + options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(CultureInfo.InvariantCulture)); using var response = await _httpClientFactory.CreateClient(NamedClient.Default) .SendAsync(options, HttpCompletionOption.ResponseHeadersRead) diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index 09525aae4e0..80a45f2b25d 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -15,7 +15,6 @@ namespace Emby.Dlna.Server { private readonly DeviceProfile _profile; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly string _serverUdn; private readonly string _serverAddress; private readonly string _serverName; @@ -193,10 +192,10 @@ namespace Emby.Dlna.Server .Append(SecurityElement.Escape(icon.MimeType ?? string.Empty)) .Append(""); builder.Append("") - .Append(SecurityElement.Escape(icon.Width.ToString(_usCulture))) + .Append(SecurityElement.Escape(icon.Width.ToString(CultureInfo.InvariantCulture))) .Append(""); builder.Append("") - .Append(SecurityElement.Escape(icon.Height.ToString(_usCulture))) + .Append(SecurityElement.Escape(icon.Height.ToString(CultureInfo.InvariantCulture))) .Append(""); builder.Append("") .Append(SecurityElement.Escape(icon.Depth ?? string.Empty)) diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 8aaa1f7bbe8..ac6606d39aa 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -23,7 +23,6 @@ namespace Emby.Server.Implementations.MediaEncoder { public class EncodingManager : IEncodingManager { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IFileSystem _fileSystem; private readonly ILogger _logger; private readonly IMediaEncoder _encoder; @@ -193,7 +192,7 @@ namespace Emby.Server.Implementations.MediaEncoder private string GetChapterImagePath(Video video, long chapterPositionTicks) { - var filename = video.DateModified.Ticks.ToString(_usCulture) + "_" + chapterPositionTicks.ToString(_usCulture) + ".jpg"; + var filename = video.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) + "_" + chapterPositionTicks.ToString(CultureInfo.InvariantCulture) + ".jpg"; return Path.Combine(GetChapterImagesPath(video), filename); } diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index b1c860d61f3..86933074d0d 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -2007,7 +2007,7 @@ namespace Jellyfin.Api.Controllers Response.Headers.Add(HeaderNames.CacheControl, "public"); } - Response.Headers.Add(HeaderNames.LastModified, dateImageModified.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss \"GMT\"", new CultureInfo("en-US", false))); + Response.Headers.Add(HeaderNames.LastModified, dateImageModified.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss \"GMT\"", CultureInfo.InvariantCulture)); // if the image was not modified since "ifModifiedSinceHeader"-header, return a HTTP status code 304 not modified if (!(dateImageModified > ifModifiedSinceHeader) && cacheDuration.HasValue) diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 11f67ee8946..1849dd04786 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -376,7 +376,7 @@ namespace Jellyfin.Api.Controllers var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks); var url = string.Format( - CultureInfo.CurrentCulture, + CultureInfo.InvariantCulture, "stream.vtt?CopyTimestamps=true&AddVttTimeMap=true&StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}", positionTicks.ToString(CultureInfo.InvariantCulture), endPositionTicks.ToString(CultureInfo.InvariantCulture), diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index 0853200dd16..afdaf448b79 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -57,9 +57,7 @@ namespace MediaBrowser.Controller.Entities public IList GetTaggedItems(InternalItemsQuery query) { - var usCulture = new CultureInfo("en-US"); - - if (!int.TryParse(Name, NumberStyles.Integer, usCulture, out var year)) + if (!int.TryParse(Name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year)) { return new List(); } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index bdb3793325a..5715194b851 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -21,8 +21,6 @@ namespace MediaBrowser.Controller.MediaEncoding { public class EncodingHelper { - private static readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private readonly IMediaEncoder _mediaEncoder; private readonly ISubtitleEncoder _subtitleEncoder; @@ -816,7 +814,7 @@ namespace MediaBrowser.Controller.MediaEncoding public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level) { - if (double.TryParse(level, NumberStyles.Any, _usCulture, out double requestLevel)) + if (double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out double requestLevel)) { if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase) || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)) @@ -911,7 +909,7 @@ namespace MediaBrowser.Controller.MediaEncoding CultureInfo.InvariantCulture, "subtitles='{0}:si={1}'{2}", _mediaEncoder.EscapeSubtitleFilterPath(mediaPath), - state.InternalSubtitleStreamOffset.ToString(_usCulture), + state.InternalSubtitleStreamOffset.ToString(CultureInfo.InvariantCulture), // fallbackFontParam, setPtsParam); } @@ -1217,7 +1215,7 @@ namespace MediaBrowser.Controller.MediaEncoding param += string.Format( CultureInfo.InvariantCulture, " -speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}", - profileScore.ToString(_usCulture), + profileScore.ToString(CultureInfo.InvariantCulture), crf, qmin, qmax); @@ -1289,7 +1287,7 @@ namespace MediaBrowser.Controller.MediaEncoding var framerate = GetFramerateParam(state); if (framerate.HasValue) { - param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(_usCulture)); + param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.InvariantCulture)); } var targetVideoCodec = state.ActualOutputVideoCodec; @@ -1393,7 +1391,7 @@ namespace MediaBrowser.Controller.MediaEncoding else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) { // hevc_qsv use -level 51 instead of -level 153. - if (double.TryParse(level, NumberStyles.Any, _usCulture, out double hevcLevel)) + if (double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out double hevcLevel)) { param += " -level " + (hevcLevel / 3); } @@ -1555,7 +1553,7 @@ namespace MediaBrowser.Controller.MediaEncoding // If a specific level was requested, the source must match or be less than var level = state.GetRequestedLevel(videoStream.Codec); if (!string.IsNullOrEmpty(level) - && double.TryParse(level, NumberStyles.Any, _usCulture, out var requestLevel)) + && double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out var requestLevel)) { if (!videoStream.Level.HasValue) { @@ -1803,7 +1801,7 @@ namespace MediaBrowser.Controller.MediaEncoding && state.AudioStream.Channels.Value > 5 && !encodingOptions.DownMixAudioBoost.Equals(1)) { - filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(_usCulture)); + filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture)); } var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive; @@ -2434,8 +2432,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (isExynosV4L2) { - var widthParam = requestedWidth.Value.ToString(_usCulture); - var heightParam = requestedHeight.Value.ToString(_usCulture); + var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture); + var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture); filters.Add( string.Format( @@ -2453,8 +2451,8 @@ namespace MediaBrowser.Controller.MediaEncoding // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size else if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue) { - var maxWidthParam = requestedMaxWidth.Value.ToString(_usCulture); - var maxHeightParam = requestedMaxHeight.Value.ToString(_usCulture); + var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture); + var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture); if (isExynosV4L2) { @@ -2486,7 +2484,7 @@ namespace MediaBrowser.Controller.MediaEncoding } else { - var widthParam = requestedWidth.Value.ToString(_usCulture); + var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture); filters.Add( string.Format( @@ -2499,7 +2497,7 @@ namespace MediaBrowser.Controller.MediaEncoding // If a fixed height was requested else if (requestedHeight.HasValue) { - var heightParam = requestedHeight.Value.ToString(_usCulture); + var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture); if (isExynosV4L2) { @@ -2522,7 +2520,7 @@ namespace MediaBrowser.Controller.MediaEncoding // If a max width was requested else if (requestedMaxWidth.HasValue) { - var maxWidthParam = requestedMaxWidth.Value.ToString(_usCulture); + var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture); if (isExynosV4L2) { @@ -2545,7 +2543,7 @@ namespace MediaBrowser.Controller.MediaEncoding // If a max height was requested else if (requestedMaxHeight.HasValue) { - var maxHeightParam = requestedMaxHeight.Value.ToString(_usCulture); + var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture); if (isExynosV4L2) { @@ -4122,12 +4120,12 @@ namespace MediaBrowser.Controller.MediaEncoding if (bitrate.HasValue) { - args += " -ab " + bitrate.Value.ToString(_usCulture); + args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); } if (state.OutputAudioSampleRate.HasValue) { - args += " -ar " + state.OutputAudioSampleRate.Value.ToString(_usCulture); + args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture); } args += GetAudioFilterParam(state, encodingOptions); @@ -4143,12 +4141,12 @@ namespace MediaBrowser.Controller.MediaEncoding if (bitrate.HasValue) { - audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(_usCulture)); + audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture)); } if (state.OutputAudioChannels.HasValue) { - audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(_usCulture)); + audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)); } // opus will fail on 44100 @@ -4156,7 +4154,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (state.OutputAudioSampleRate.HasValue) { - audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(_usCulture)); + audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture)); } } diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs index c4ddc5618dd..933f440ac66 100644 --- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs +++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs @@ -13,7 +13,6 @@ namespace MediaBrowser.Controller.MediaEncoding { public class JobLogger { - private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US")); private readonly ILogger _logger; public JobLogger(ILogger logger) @@ -87,7 +86,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var rate = parts[i + 1]; - if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val)) + if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val)) { framerate = val; } @@ -96,7 +95,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var rate = part.Split('=', 2)[^1]; - if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val)) + if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val)) { framerate = val; } @@ -106,7 +105,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var time = part.Split('=', 2)[^1]; - if (TimeSpan.TryParse(time, _usCulture, out var val)) + if (TimeSpan.TryParse(time, CultureInfo.InvariantCulture, out var val)) { var currentMs = startMs + val.TotalMilliseconds; @@ -128,7 +127,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (scale.HasValue) { - if (long.TryParse(size, NumberStyles.Any, _usCulture, out var val)) + if (long.TryParse(size, NumberStyles.Any, CultureInfo.InvariantCulture, out var val)) { bytesTranscoded = val * scale.Value; } @@ -147,7 +146,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (scale.HasValue) { - if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val)) + if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val)) { bitRate = (int)Math.Ceiling(val * scale.Value); } diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index b7398880efa..988581df92b 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -60,8 +60,6 @@ namespace MediaBrowser.LocalMetadata.Images private readonly IFileSystem _fileSystem; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - /// /// Initializes a new instance of the class. /// @@ -434,7 +432,7 @@ namespace MediaBrowser.LocalMetadata.Images var seasonMarker = seasonNumber.Value == 0 ? "-specials" - : seasonNumber.Value.ToString("00", _usCulture); + : seasonNumber.Value.ToString("00", CultureInfo.InvariantCulture); // Get this one directly from the file system since we have to go up a level if (!string.Equals(prefix, seasonMarker, StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 7c9e681d6ac..5a36c16630b 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -21,8 +21,6 @@ namespace MediaBrowser.LocalMetadata.Parsers public class BaseItemXmlParser where T : BaseItem { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private Dictionary? _validProviderIds; /// @@ -180,7 +178,7 @@ namespace MediaBrowser.LocalMetadata.Parsers if (!string.IsNullOrEmpty(text)) { - if (float.TryParse(text, NumberStyles.Any, _usCulture, out var value)) + if (float.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { item.CriticRating = value; } @@ -332,7 +330,7 @@ namespace MediaBrowser.LocalMetadata.Parsers if (!string.IsNullOrWhiteSpace(text)) { - if (int.TryParse(text.AsSpan().LeftPart(' '), NumberStyles.Integer, _usCulture, out var runtime)) + if (int.TryParse(text.AsSpan().LeftPart(' '), NumberStyles.Integer, CultureInfo.InvariantCulture, out var runtime)) { item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks; } @@ -1095,7 +1093,7 @@ namespace MediaBrowser.LocalMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var intVal)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intVal)) { sortOrder = intVal; } diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index 6a3896eb6f0..6f66fd61b01 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -25,8 +25,6 @@ namespace MediaBrowser.LocalMetadata.Savers /// public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss"; - private static readonly CultureInfo _usCulture = new CultureInfo("en-US"); - /// /// Initializes a new instance of the class. /// @@ -205,7 +203,7 @@ namespace MediaBrowser.LocalMetadata.Savers if (item.CriticRating.HasValue) { - writer.WriteElementString("CriticRating", item.CriticRating.Value.ToString(_usCulture)); + writer.WriteElementString("CriticRating", item.CriticRating.Value.ToString(CultureInfo.InvariantCulture)); } if (!string.IsNullOrEmpty(item.Overview)) @@ -289,12 +287,12 @@ namespace MediaBrowser.LocalMetadata.Savers if (item.CommunityRating.HasValue) { - writer.WriteElementString("Rating", item.CommunityRating.Value.ToString(_usCulture)); + writer.WriteElementString("Rating", item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)); } if (item.ProductionYear.HasValue && item is not Person) { - writer.WriteElementString("ProductionYear", item.ProductionYear.Value.ToString(_usCulture)); + writer.WriteElementString("ProductionYear", item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture)); } if (item is IHasAspectRatio hasAspectRatio) @@ -322,7 +320,7 @@ namespace MediaBrowser.LocalMetadata.Savers { var timespan = TimeSpan.FromTicks(runTimeTicks.Value); - writer.WriteElementString("RunningTime", Math.Floor(timespan.TotalMinutes).ToString(_usCulture)); + writer.WriteElementString("RunningTime", Math.Floor(timespan.TotalMinutes).ToString(CultureInfo.InvariantCulture)); } if (item.ProviderIds != null) @@ -395,7 +393,7 @@ namespace MediaBrowser.LocalMetadata.Savers if (person.SortOrder.HasValue) { - writer.WriteElementString("SortOrder", person.SortOrder.Value.ToString(_usCulture)); + writer.WriteElementString("SortOrder", person.SortOrder.Value.ToString(CultureInfo.InvariantCulture)); } writer.WriteEndElement(); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 4cbd1bbc807..06fe95ce82e 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -43,11 +43,6 @@ namespace MediaBrowser.MediaEncoding.Encoder /// internal const int DefaultHdrImageExtractionTimeout = 20000; - /// - /// The us culture. - /// - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private readonly ILogger _logger; private readonly IServerConfigurationManager _configurationManager; private readonly IFileSystem _fileSystem; @@ -687,7 +682,7 @@ namespace MediaBrowser.MediaEncoding.Encoder public string GetTimeParameter(TimeSpan time) { - return time.ToString(@"hh\:mm\:ss\.fff", _usCulture); + return time.ToString(@"hh\:mm\:ss\.fff", CultureInfo.InvariantCulture); } public async Task ExtractVideoImagesOnInterval( @@ -704,11 +699,11 @@ namespace MediaBrowser.MediaEncoding.Encoder { var inputArgument = GetInputArgument(inputFile, mediaSource); - var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(_usCulture); + var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(CultureInfo.InvariantCulture); if (maxWidth.HasValue) { - var maxWidthParam = maxWidth.Value.ToString(_usCulture); + var maxWidthParam = maxWidth.Value.ToString(CultureInfo.InvariantCulture); vf += string.Format(CultureInfo.InvariantCulture, ",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam); } diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 26f629a315b..9ed6c264eea 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -30,7 +30,6 @@ namespace MediaBrowser.MediaEncoding.Probing private static readonly Regex _performerPattern = new (@"(?.*) \((?.*)\)"); - private readonly CultureInfo _usCulture = new ("en-US"); private readonly ILogger _logger; private readonly ILocalizationManager _localization; @@ -83,7 +82,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (!string.IsNullOrEmpty(data.Format.BitRate)) { - if (int.TryParse(data.Format.BitRate, NumberStyles.Any, _usCulture, out var value)) + if (int.TryParse(data.Format.BitRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { info.Bitrate = value; } @@ -191,7 +190,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (data.Format != null && !string.IsNullOrEmpty(data.Format.Duration)) { - info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.Format.Duration, _usCulture)).Ticks; + info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.Format.Duration, CultureInfo.InvariantCulture)).Ticks; } FetchWtvInfo(info, data); @@ -673,7 +672,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (!string.IsNullOrEmpty(streamInfo.SampleRate)) { - if (int.TryParse(streamInfo.SampleRate, NumberStyles.Any, _usCulture, out var value)) + if (int.TryParse(streamInfo.SampleRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { stream.SampleRate = value; } @@ -802,7 +801,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (!string.IsNullOrEmpty(streamInfo.BitRate)) { - if (int.TryParse(streamInfo.BitRate, NumberStyles.Any, _usCulture, out var value)) + if (int.TryParse(streamInfo.BitRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { bitrate = value; } @@ -815,7 +814,7 @@ namespace MediaBrowser.MediaEncoding.Probing && (stream.Type == MediaStreamType.Video || (isAudio && stream.Type == MediaStreamType.Audio))) { // If the stream info doesn't have a bitrate get the value from the media format info - if (int.TryParse(formatInfo.BitRate, NumberStyles.Any, _usCulture, out var value)) + if (int.TryParse(formatInfo.BitRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { bitrate = value; } @@ -921,8 +920,8 @@ namespace MediaBrowser.MediaEncoding.Probing var parts = (original ?? string.Empty).Split(':'); if (!(parts.Length == 2 && - int.TryParse(parts[0], NumberStyles.Any, _usCulture, out var width) && - int.TryParse(parts[1], NumberStyles.Any, _usCulture, out var height) && + int.TryParse(parts[0], NumberStyles.Any, CultureInfo.InvariantCulture, out var width) && + int.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var height) && width > 0 && height > 0)) { @@ -1008,11 +1007,11 @@ namespace MediaBrowser.MediaEncoding.Probing if (parts.Length == 2) { - result = float.Parse(parts[0], _usCulture) / float.Parse(parts[1], _usCulture); + result = float.Parse(parts[0], CultureInfo.InvariantCulture) / float.Parse(parts[1], CultureInfo.InvariantCulture); } else { - result = float.Parse(parts[0], _usCulture); + result = float.Parse(parts[0], CultureInfo.InvariantCulture); } return float.IsNaN(result) ? null : result; @@ -1039,7 +1038,7 @@ namespace MediaBrowser.MediaEncoding.Probing // If we got something, parse it if (!string.IsNullOrEmpty(duration)) { - data.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks; + data.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, CultureInfo.InvariantCulture)).Ticks; } } @@ -1101,7 +1100,7 @@ namespace MediaBrowser.MediaEncoding.Probing return; } - info.Size = string.IsNullOrEmpty(data.Format.Size) ? null : long.Parse(data.Format.Size, _usCulture); + info.Size = string.IsNullOrEmpty(data.Format.Size) ? null : long.Parse(data.Format.Size, CultureInfo.InvariantCulture); } private void SetAudioInfoFromTags(MediaInfo audio, IReadOnlyDictionary tags) @@ -1144,7 +1143,7 @@ namespace MediaBrowser.MediaEncoding.Probing { Name = match.Groups["name"].Value, Type = PersonType.Actor, - Role = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(match.Groups["instrument"].Value) + Role = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(match.Groups["instrument"].Value) }); } } @@ -1443,7 +1442,7 @@ namespace MediaBrowser.MediaEncoding.Probing .ToArray(); } - if (tags.TryGetValue("WM/OriginalReleaseTime", out var year) && int.TryParse(year, NumberStyles.Integer, _usCulture, out var parsedYear)) + if (tags.TryGetValue("WM/OriginalReleaseTime", out var year) && int.TryParse(year, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedYear)) { video.ProductionYear = parsedYear; } diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs index b213e7aa001..406d32cde15 100644 --- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs +++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs @@ -56,10 +56,10 @@ namespace MediaBrowser.Model.Globalization IEnumerable GetLocalizationOptions(); /// - /// Returns the correct for the given language. + /// Returns the correct for the given language. /// /// The language. - /// The correct for the given language. + /// The correct for the given language. CultureDto? FindLanguageInfo(string language); } } diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 7d259a9d36a..4b05edd67ba 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -29,8 +29,6 @@ namespace MediaBrowser.Providers.Manager /// public class ImageSaver { - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - /// /// The _config. /// @@ -377,7 +375,7 @@ namespace MediaBrowser.Providers.Manager var seasonMarker = season.IndexNumber.Value == 0 ? "-specials" - : season.IndexNumber.Value.ToString("00", UsCulture); + : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); var imageFilename = "season" + seasonMarker + "-landscape" + extension; @@ -400,7 +398,7 @@ namespace MediaBrowser.Providers.Manager var seasonMarker = season.IndexNumber.Value == 0 ? "-specials" - : season.IndexNumber.Value.ToString("00", UsCulture); + : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); var imageFilename = "season" + seasonMarker + "-banner" + extension; @@ -495,12 +493,12 @@ namespace MediaBrowser.Providers.Manager var filenames = images.Select(i => Path.GetFileNameWithoutExtension(i.Path)).ToList(); var current = 1; - while (filenames.Contains(numberedIndexPrefix + current.ToString(UsCulture), StringComparer.OrdinalIgnoreCase)) + while (filenames.Contains(numberedIndexPrefix + current.ToString(CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { current++; } - return numberedIndexPrefix + current.ToString(UsCulture); + return numberedIndexPrefix + current.ToString(CultureInfo.InvariantCulture); } /// @@ -539,7 +537,7 @@ namespace MediaBrowser.Providers.Manager var seasonMarker = season.IndexNumber.Value == 0 ? "-specials" - : season.IndexNumber.Value.ToString("00", UsCulture); + : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); var imageFilename = "season" + seasonMarker + "-fanart" + extension; @@ -556,7 +554,7 @@ namespace MediaBrowser.Providers.Manager if (item.IsInMixedFolder) { - return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(UsCulture), extension) }; + return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(CultureInfo.InvariantCulture), extension) }; } var extraFanartFilename = GetBackdropSaveFilename(item.GetImages(ImageType.Backdrop), "fanart", "fanart", outputIndex); @@ -568,7 +566,7 @@ namespace MediaBrowser.Providers.Manager if (EnableExtraThumbsDuplication) { - list.Add(Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension)); + list.Add(Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(CultureInfo.InvariantCulture) + extension)); } return list.ToArray(); @@ -582,7 +580,7 @@ namespace MediaBrowser.Providers.Manager var seasonMarker = season.IndexNumber.Value == 0 ? "-specials" - : season.IndexNumber.Value.ToString("00", UsCulture); + : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); var imageFilename = "season" + seasonMarker + "-poster" + extension; diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index 479ae0f3918..b2bc58eea1a 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -26,7 +26,6 @@ namespace MediaBrowser.Providers.Plugins.Omdb private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; private readonly IHttpClientFactory _httpClientFactory; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IApplicationHost _appHost; private readonly JsonSerializerOptions _jsonOptions; @@ -79,7 +78,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb } if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4 - && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, _usCulture, out var year) + && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, CultureInfo.InvariantCulture, out var year) && year >= 0) { item.ProductionYear = year; @@ -93,14 +92,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb } if (!string.IsNullOrEmpty(result.imdbVotes) - && int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out var voteCount) + && int.TryParse(result.imdbVotes, NumberStyles.Number, CultureInfo.InvariantCulture, out var voteCount) && voteCount >= 0) { // item.VoteCount = voteCount; } if (!string.IsNullOrEmpty(result.imdbRating) - && float.TryParse(result.imdbRating, NumberStyles.Any, _usCulture, out var imdbRating) + && float.TryParse(result.imdbRating, NumberStyles.Any, CultureInfo.InvariantCulture, out var imdbRating) && imdbRating >= 0) { item.CommunityRating = imdbRating; @@ -191,7 +190,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb } if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4 - && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, _usCulture, out var year) + && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, CultureInfo.InvariantCulture, out var year) && year >= 0) { item.ProductionYear = year; @@ -205,14 +204,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb } if (!string.IsNullOrEmpty(result.imdbVotes) - && int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out var voteCount) + && int.TryParse(result.imdbVotes, NumberStyles.Number, CultureInfo.InvariantCulture, out var voteCount) && voteCount >= 0) { // item.VoteCount = voteCount; } if (!string.IsNullOrEmpty(result.imdbRating) - && float.TryParse(result.imdbRating, NumberStyles.Any, _usCulture, out var imdbRating) + && float.TryParse(result.imdbRating, NumberStyles.Any, CultureInfo.InvariantCulture, out var imdbRating) && imdbRating >= 0) { item.CommunityRating = imdbRating; diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index f7f4ea06523..9d558b6ce73 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -58,8 +58,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers _directoryService = directoryService; } - protected CultureInfo UsCulture { get; } = new CultureInfo("en-US"); - /// /// Gets the logger. /// @@ -309,7 +307,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrEmpty(text)) { - if (float.TryParse(text, NumberStyles.Any, UsCulture, out var value)) + if (float.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { item.CriticRating = value; } @@ -370,7 +368,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val) && userData != null) { - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var count)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var count)) { userData.PlayCount = count; } @@ -475,7 +473,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(text)) { - if (int.TryParse(text.AsSpan().LeftPart(' '), NumberStyles.Integer, UsCulture, out var runtime)) + if (int.TryParse(text.AsSpan().LeftPart(' '), NumberStyles.Integer, CultureInfo.InvariantCulture, out var runtime)) { item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks; } @@ -1265,7 +1263,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var intVal)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intVal)) { sortOrder = intVal; } diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs index 3a305024ee4..d2f349ad7a2 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs @@ -163,7 +163,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { // int.TryParse is local aware, so it can be problematic, force us culture - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rval)) { item.AirsBeforeEpisodeNumber = rval; } @@ -179,7 +179,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { // int.TryParse is local aware, so it can be problematic, force us culture - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rval)) { item.AirsAfterSeasonNumber = rval; } @@ -195,7 +195,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { // int.TryParse is local aware, so it can be problematic, force us culture - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rval)) { item.AirsBeforeSeasonNumber = rval; } @@ -211,7 +211,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { // int.TryParse is local aware, so it can be problematic, force us culture - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rval)) { item.AirsBeforeSeasonNumber = rval; } @@ -227,7 +227,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { // int.TryParse is local aware, so it can be problematic, force us culture - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rval)) { item.AirsBeforeEpisodeNumber = rval; } diff --git a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs index 62f80e81bd0..2cd3fdf0285 100644 --- a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs @@ -17,8 +17,6 @@ namespace MediaBrowser.XbmcMetadata.Savers /// public class EpisodeNfoSaver : BaseNfoSaver { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - /// /// Initializes a new instance of the class. /// @@ -60,17 +58,17 @@ namespace MediaBrowser.XbmcMetadata.Savers if (episode.IndexNumber.HasValue) { - writer.WriteElementString("episode", episode.IndexNumber.Value.ToString(_usCulture)); + writer.WriteElementString("episode", episode.IndexNumber.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.IndexNumberEnd.HasValue) { - writer.WriteElementString("episodenumberend", episode.IndexNumberEnd.Value.ToString(_usCulture)); + writer.WriteElementString("episodenumberend", episode.IndexNumberEnd.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.ParentIndexNumber.HasValue) { - writer.WriteElementString("season", episode.ParentIndexNumber.Value.ToString(_usCulture)); + writer.WriteElementString("season", episode.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.PremiereDate.HasValue) @@ -84,28 +82,28 @@ namespace MediaBrowser.XbmcMetadata.Savers { if (episode.AirsAfterSeasonNumber.HasValue && episode.AirsAfterSeasonNumber.Value != -1) { - writer.WriteElementString("airsafter_season", episode.AirsAfterSeasonNumber.Value.ToString(_usCulture)); + writer.WriteElementString("airsafter_season", episode.AirsAfterSeasonNumber.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.AirsBeforeEpisodeNumber.HasValue && episode.AirsBeforeEpisodeNumber.Value != -1) { - writer.WriteElementString("airsbefore_episode", episode.AirsBeforeEpisodeNumber.Value.ToString(_usCulture)); + writer.WriteElementString("airsbefore_episode", episode.AirsBeforeEpisodeNumber.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.AirsBeforeSeasonNumber.HasValue && episode.AirsBeforeSeasonNumber.Value != -1) { - writer.WriteElementString("airsbefore_season", episode.AirsBeforeSeasonNumber.Value.ToString(_usCulture)); + writer.WriteElementString("airsbefore_season", episode.AirsBeforeSeasonNumber.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.AirsBeforeEpisodeNumber.HasValue && episode.AirsBeforeEpisodeNumber.Value != -1) { - writer.WriteElementString("displayepisode", episode.AirsBeforeEpisodeNumber.Value.ToString(_usCulture)); + writer.WriteElementString("displayepisode", episode.AirsBeforeEpisodeNumber.Value.ToString(CultureInfo.InvariantCulture)); } var specialSeason = episode.AiredSeasonNumber; if (specialSeason.HasValue && specialSeason.Value != -1) { - writer.WriteElementString("displayseason", specialSeason.Value.ToString(_usCulture)); + writer.WriteElementString("displayseason", specialSeason.Value.ToString(CultureInfo.InvariantCulture)); } } } From 7668cd13f559d25833ad53e85e939f1d7b836111 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 26 Sep 2021 16:23:43 -0600 Subject: [PATCH 140/278] Create output directory when extracting archive files --- Emby.Server.Implementations/Archiving/ZipClient.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Emby.Server.Implementations/Archiving/ZipClient.cs b/Emby.Server.Implementations/Archiving/ZipClient.cs index 591ae547d6a..9e1d550ebcd 100644 --- a/Emby.Server.Implementations/Archiving/ZipClient.cs +++ b/Emby.Server.Implementations/Archiving/ZipClient.cs @@ -45,6 +45,7 @@ namespace Emby.Server.Implementations.Archiving options.Overwrite = true; } + Directory.CreateDirectory(targetPath); reader.WriteAllToDirectory(targetPath, options); } @@ -58,6 +59,7 @@ namespace Emby.Server.Implementations.Archiving Overwrite = overwriteExistingFiles }; + Directory.CreateDirectory(targetPath); reader.WriteAllToDirectory(targetPath, options); } @@ -71,6 +73,7 @@ namespace Emby.Server.Implementations.Archiving Overwrite = overwriteExistingFiles }; + Directory.CreateDirectory(targetPath); reader.WriteAllToDirectory(targetPath, options); } @@ -120,6 +123,7 @@ namespace Emby.Server.Implementations.Archiving Overwrite = overwriteExistingFiles }; + Directory.CreateDirectory(targetPath); reader.WriteAllToDirectory(targetPath, options); } @@ -151,6 +155,7 @@ namespace Emby.Server.Implementations.Archiving Overwrite = overwriteExistingFiles }; + Directory.CreateDirectory(targetPath); reader.WriteAllToDirectory(targetPath, options); } } From 5b1feb1cea9e8b7dab31ea65f5d3b3b5f0b1f045 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Sep 2021 12:00:58 +0000 Subject: [PATCH 141/278] Bump Swashbuckle.AspNetCore from 6.2.1 to 6.2.2 Bumps [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) from 6.2.1 to 6.2.2. - [Release notes](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases) - [Commits](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v6.2.1...v6.2.2) --- updated-dependencies: - dependency-name: Swashbuckle.AspNetCore dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 8cc4711a729..57c7d215b08 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -16,7 +16,7 @@ - + From 3d098173ff46a9b9ce9c8acaff92bc9c06807112 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Sep 2021 09:11:16 +0000 Subject: [PATCH 142/278] Bump Swashbuckle.AspNetCore.ReDoc from 6.2.1 to 6.2.2 Bumps [Swashbuckle.AspNetCore.ReDoc](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) from 6.2.1 to 6.2.2. - [Release notes](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases) - [Commits](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v6.2.1...v6.2.2) --- updated-dependencies: - dependency-name: Swashbuckle.AspNetCore.ReDoc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 57c7d215b08..1c451ef6c19 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -17,7 +17,7 @@ - + From 055e04338ed59c3ec62485e12da3f9a86442edaf Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 28 Sep 2021 12:03:32 +0200 Subject: [PATCH 143/278] Add regression test for #6560 --- .../Test Data/Updates/empty.zip | Bin 0 -> 162 bytes .../Updates/InstallationManagerTests.cs | 33 +++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/empty.zip diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/empty.zip b/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/empty.zip new file mode 100644 index 0000000000000000000000000000000000000000..15628e26b3807ccca649c76b689f3c07059c4928 GIT binary patch literal 162 zcmWIWW@h1H00G;0n_w^lO0Y4=Fr?-dlvL`6hHx@4t33}&)C1zu3T_5QmKV$n3}7O_ pn~_P58J7tXP_qOW-a3MqF!NX;=3$t{$_7%!2!uXB+5yC2007Nw7nT43 literal 0 HcmV?d00001 diff --git a/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs index 70acbfc40e0..09c4bd1004d 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs @@ -6,7 +6,9 @@ using System.Threading; using System.Threading.Tasks; using AutoFixture; using AutoFixture.AutoMoq; +using Emby.Server.Implementations.Archiving; using Emby.Server.Implementations.Updates; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Updates; using Moq; using Moq.Protected; @@ -40,7 +42,9 @@ namespace Jellyfin.Server.Implementations.Tests.Updates _fixture.Customize(new AutoMoqCustomization { ConfigureMembers = true - }).Inject(http); + }); + _fixture.Inject(http); + _fixture.Inject(new ZipClient()); _installationManager = _fixture.Create(); } @@ -78,5 +82,32 @@ namespace Jellyfin.Server.Implementations.Tests.Updates packages = _installationManager.FilterPackages(packages, id: new Guid("a4df60c5-6ab4-412a-8f79-2cab93fb2bc5")).ToArray(); Assert.Single(packages); } + + [Fact] + public async Task InstallPackage_InvalidChecksum_ThrowsInvalidDataException() + { + var packageInfo = new InstallationInfo() + { + Name = "Test", + SourceUrl = "https://repo.jellyfin.org/releases/plugin/empty/empty.zip", + Checksum = "InvalidChecksum" + }; + + await Assert.ThrowsAsync(() => _installationManager.InstallPackage(packageInfo, CancellationToken.None)).ConfigureAwait(false); + } + + [Fact] + public async Task InstallPackage_Valid_Success() + { + var packageInfo = new InstallationInfo() + { + Name = "Test", + SourceUrl = "https://repo.jellyfin.org/releases/plugin/empty/empty.zip", + Checksum = "11b5b2f1a9ebc4f66d6ef19018543361" + }; + + var ex = await Record.ExceptionAsync(() => _installationManager.InstallPackage(packageInfo, CancellationToken.None)).ConfigureAwait(false); + Assert.Null(ex); + } } } From 6fb3ec0319e91a0557a1ba5d3139e938aa5dd154 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Tue, 28 Sep 2021 19:09:37 -0600 Subject: [PATCH 144/278] Disable DOTNET_SYSTEM_GLOBALIZATION_INVARIANT in docker images --- Dockerfile | 2 +- Dockerfile.arm | 2 +- Dockerfile.arm64 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 73b5908b4ef..e655d62f379 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,7 +61,7 @@ RUN apt-get update \ && chmod 777 /cache /config /media \ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen -ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 +# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 ENV LC_ALL en_US.UTF-8 ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en diff --git a/Dockerfile.arm b/Dockerfile.arm index edb8591c648..f961d6482dd 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -50,7 +50,7 @@ RUN apt-get update \ && chmod 777 /cache /config /media \ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen -ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 +# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 ENV LC_ALL en_US.UTF-8 ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index db1edcfe66f..aa6a9889207 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -40,7 +40,7 @@ RUN apt-get update && apt-get install --no-install-recommends --no-install-sugge && chmod 777 /cache /config /media \ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen -ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 +# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 ENV LC_ALL en_US.UTF-8 ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en From 1a70ba6a486dfac16ea4e65b8b075cb6fb627f30 Mon Sep 17 00:00:00 2001 From: Chris Tam Date: Wed, 29 Sep 2021 16:31:17 -0400 Subject: [PATCH 145/278] Update README for .NET version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3aef84b9915..6653bff116e 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ These instructions will help you get set up with a local development environment ### Prerequisites -Before the project can be built, you must first install the [.NET 5.0 SDK](https://dotnet.microsoft.com/download) on your system. +Before the project can be built, you must first install the [.NET 6.0 SDK](https://dotnet.microsoft.com/download/dotnet) on your system. Instructions to run this project from the command line are included here, but you will also need to install an IDE if you want to debug the server while it is running. Any IDE that supports .NET Core development will work, but two options are recent versions of [Visual Studio](https://visualstudio.microsoft.com/downloads/) (at least 2017) and [Visual Studio Code](https://code.visualstudio.com/Download). From 7bf2ece20e11cefca9160d75cd0d9e80bb04fe46 Mon Sep 17 00:00:00 2001 From: Tim040 Date: Wed, 29 Sep 2021 14:19:51 +0000 Subject: [PATCH 146/278] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nl/ --- Emby.Server.Implementations/Localization/Core/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index f79840c781e..79f921bcb20 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -15,7 +15,7 @@ "Favorites": "Favorieten", "Folders": "Mappen", "Genres": "Genres", - "HeaderAlbumArtists": "Albumartiesten", + "HeaderAlbumArtists": "Artiests Album", "HeaderContinueWatching": "Kijken hervatten", "HeaderFavoriteAlbums": "Favoriete albums", "HeaderFavoriteArtists": "Favoriete artiesten", From ce0457faf5dd0ecc7413fbb3618b52e1f0636446 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Thu, 30 Sep 2021 22:24:46 +0200 Subject: [PATCH 147/278] Add mp4 track title fallback (#6638) --- .../Probing/ProbeResultNormalizer.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 9ed6c264eea..df9753b3808 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -688,6 +688,16 @@ namespace MediaBrowser.MediaEncoding.Probing { stream.BitDepth = streamInfo.BitsPerRawSample; } + + if (string.IsNullOrEmpty(stream.Title)) + { + // mp4 missing track title workaround: fall back to handler_name if populated + string handlerName = GetDictionaryValue(streamInfo.Tags, "handler_name"); + if (!string.IsNullOrEmpty(handlerName)) + { + stream.Title = handlerName; + } + } } else if (string.Equals(streamInfo.CodecType, "subtitle", StringComparison.OrdinalIgnoreCase)) { @@ -696,6 +706,16 @@ namespace MediaBrowser.MediaEncoding.Probing stream.LocalizedUndefined = _localization.GetLocalizedString("Undefined"); stream.LocalizedDefault = _localization.GetLocalizedString("Default"); stream.LocalizedForced = _localization.GetLocalizedString("Forced"); + + if (string.IsNullOrEmpty(stream.Title)) + { + // mp4 missing track title workaround: fall back to handler_name if populated and not the default "SubtitleHandler" + string handlerName = GetDictionaryValue(streamInfo.Tags, "handler_name"); + if (!string.IsNullOrEmpty(handlerName) && !string.Equals(handlerName, "SubtitleHandler", StringComparison.OrdinalIgnoreCase)) + { + stream.Title = handlerName; + } + } } else if (string.Equals(streamInfo.CodecType, "video", StringComparison.OrdinalIgnoreCase)) { From d4373a2ddb3df360554db7a949d8b92e135e63a8 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 1 Oct 2021 10:54:06 +0200 Subject: [PATCH 148/278] Use ConvertFrom with invariant culture instead of current culture --- .../Json/Converters/JsonDelimitedArrayConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs index 3c7d504b101..51b955145f1 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs @@ -44,7 +44,7 @@ namespace Jellyfin.Extensions.Json.Converters { try { - parsedValues[i] = _typeConverter.ConvertFrom(stringEntries[i].Trim()) ?? throw new FormatException(); + parsedValues[i] = _typeConverter.ConvertFromInvariantString(stringEntries[i].Trim()) ?? throw new FormatException(); convertedCount++; } catch (FormatException) From 1ee58bf0200986cbddcab8ce9fd4fa930bee84e1 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 1 Oct 2021 11:21:22 +0200 Subject: [PATCH 149/278] Enable nullable for ItemDataProvider --- .../LiveTv/EmbyTV/ItemDataProvider.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index 4a031e4752f..46979bfc570 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs @@ -1,9 +1,8 @@ -#nullable disable - #pragma warning disable CS1591 using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text.Json; @@ -18,7 +17,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private readonly string _dataPath; private readonly object _fileDataLock = new object(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; - private T[] _items; + private T[]? _items; public ItemDataProvider( ILogger logger, @@ -34,6 +33,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV protected Func EqualityComparer { get; } + [MemberNotNull(nameof(_items))] private void EnsureLoaded() { if (_items != null) @@ -49,6 +49,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var bytes = File.ReadAllBytes(_dataPath); _items = JsonSerializer.Deserialize(bytes, _jsonOptions); + if (_items == null) + { + Logger.LogError("Error deserializing {Path}, data was null", _dataPath); + _items = Array.Empty(); + } + return; } catch (JsonException ex) @@ -62,7 +68,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private void SaveList() { - Directory.CreateDirectory(Path.GetDirectoryName(_dataPath)); + Directory.CreateDirectory(Path.GetDirectoryName(_dataPath) ?? throw new ArgumentException("Path can't be a root directory.", nameof(_dataPath))); var jsonString = JsonSerializer.Serialize(_items, _jsonOptions); File.WriteAllText(_dataPath, jsonString); } From 4c2adc39c72bb88b3c1bf2f47d53170d45075c69 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Fri, 1 Oct 2021 21:47:42 +0200 Subject: [PATCH 150/278] Add test data and unit test for mp4 metadata probe --- .../Probing/ProbeResultNormalizerTests.cs | 68 +++++ .../Test Data/Probing/video_mp4_metadata.json | 260 ++++++++++++++++++ 2 files changed, 328 insertions(+) create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_mp4_metadata.json diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index d0d472e4d43..4504924cbf1 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -5,8 +5,10 @@ using System.Text.Json; using Jellyfin.Extensions.Json; using MediaBrowser.MediaEncoding.Probing; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging.Abstractions; +using Moq; using Xunit; namespace Jellyfin.MediaEncoding.Tests.Probing @@ -55,6 +57,72 @@ namespace Jellyfin.MediaEncoding.Tests.Probing Assert.Equal("Just color bars", res.Overview); } + [Fact] + public void GetMediaInfo_Mp4MetaData_Success() + { + var bytes = File.ReadAllBytes("Test Data/Probing/video_mp4_metadata.json"); + var internalMediaInfoResult = JsonSerializer.Deserialize(bytes, _jsonOptions); + + // subtitle handling requires a localization object, set a mock to return the input string + var mockLocalization = new Mock(); + mockLocalization.Setup(x => x.GetLocalizedString(It.IsAny())).Returns(x => x); + ProbeResultNormalizer localizedProbeResultNormalizer = new ProbeResultNormalizer(new NullLogger(), mockLocalization.Object); + + MediaInfo res = localizedProbeResultNormalizer.GetMediaInfo(internalMediaInfoResult, VideoType.VideoFile, false, "Test Data/Probing/video_mp4_metadata.mkv", MediaProtocol.File); + + // [Video, Audio (Main), Audio (Commentary), Subtitle (Main, Spanish), Subtitle (Main, English), Subtitle (Commentary) + Assert.Equal(6, res.MediaStreams.Count); + + Assert.NotNull(res.VideoStream); + Assert.Equal(res.MediaStreams[0], res.VideoStream); + Assert.Equal(0, res.VideoStream.Index); + Assert.Equal("h264", res.VideoStream.Codec); + Assert.Equal("High", res.VideoStream.Profile); + Assert.Equal(MediaStreamType.Video, res.VideoStream.Type); + Assert.Equal(358, res.VideoStream.Height); + Assert.Equal(720, res.VideoStream.Width); + Assert.Equal("2.40:1", res.VideoStream.AspectRatio); + Assert.Equal("yuv420p", res.VideoStream.PixelFormat); + Assert.Equal(31d, res.VideoStream.Level); + Assert.Equal(1, res.VideoStream.RefFrames); + Assert.True(res.VideoStream.IsAVC); + Assert.Equal(120f, res.VideoStream.RealFrameRate); + Assert.Equal("1/90000", res.VideoStream.TimeBase); + Assert.Equal(1147365, res.VideoStream.BitRate); + Assert.Equal(8, res.VideoStream.BitDepth); + Assert.True(res.VideoStream.IsDefault); + Assert.Equal("und", res.VideoStream.Language); + + Assert.Equal(MediaStreamType.Audio, res.MediaStreams[1].Type); + Assert.Equal("aac", res.MediaStreams[1].Codec); + Assert.Equal(7, res.MediaStreams[1].Channels); + Assert.True(res.MediaStreams[1].IsDefault); + Assert.Equal("eng", res.MediaStreams[1].Language); + Assert.Equal("Surround 6.1", res.MediaStreams[1].Title); + + Assert.Equal(MediaStreamType.Audio, res.MediaStreams[2].Type); + Assert.Equal("aac", res.MediaStreams[2].Codec); + Assert.Equal(2, res.MediaStreams[2].Channels); + Assert.False(res.MediaStreams[2].IsDefault); + Assert.Equal("eng", res.MediaStreams[2].Language); + Assert.Equal("Commentary", res.MediaStreams[2].Title); + + Assert.Equal("spa", res.MediaStreams[3].Language); + Assert.Equal(MediaStreamType.Subtitle, res.MediaStreams[3].Type); + Assert.Equal("DVDSUB", res.MediaStreams[3].Codec); + Assert.Null(res.MediaStreams[3].Title); + + Assert.Equal("eng", res.MediaStreams[4].Language); + Assert.Equal(MediaStreamType.Subtitle, res.MediaStreams[4].Type); + Assert.Equal("mov_text", res.MediaStreams[4].Codec); + Assert.Null(res.MediaStreams[4].Title); + + Assert.Equal("eng", res.MediaStreams[5].Language); + Assert.Equal(MediaStreamType.Subtitle, res.MediaStreams[5].Type); + Assert.Equal("mov_text", res.MediaStreams[5].Codec); + Assert.Equal("Commentary", res.MediaStreams[5].Title); + } + [Fact] public void GetMediaInfo_MusicVideo_Success() { diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_mp4_metadata.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_mp4_metadata.json new file mode 100644 index 00000000000..77e3def76d5 --- /dev/null +++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_mp4_metadata.json @@ -0,0 +1,260 @@ +{ + "streams": [ + { + "index": 0, + "codec_name": "h264", + "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", + "profile": "High", + "codec_type": "video", + "codec_tag_string": "avc1", + "codec_tag": "0x31637661", + "width": 720, + "height": 358, + "coded_width": 720, + "coded_height": 358, + "closed_captions": 0, + "has_b_frames": 2, + "sample_aspect_ratio": "32:27", + "display_aspect_ratio": "1280:537", + "pix_fmt": "yuv420p", + "level": 31, + "color_range": "tv", + "color_space": "smpte170m", + "color_transfer": "bt709", + "color_primaries": "smpte170m", + "chroma_location": "left", + "refs": 1, + "is_avc": "true", + "nal_length_size": "4", + "r_frame_rate": "120/1", + "avg_frame_rate": "1704753000/71073479", + "time_base": "1/90000", + "start_pts": 0, + "start_time": "0.000000", + "duration_ts": 1421469580, + "duration": "15794.106444", + "bit_rate": "1147365", + "bits_per_raw_sample": "8", + "nb_frames": "378834", + "disposition": { + "default": 1, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0 + }, + "tags": { + "creation_time": "2021-09-13T22:42:42.000000Z", + "language": "und", + "handler_name": "VideoHandler", + "vendor_id": "[0][0][0][0]" + } + }, + { + "index": 1, + "codec_name": "aac", + "codec_long_name": "AAC (Advanced Audio Coding)", + "profile": "LC", + "codec_type": "audio", + "codec_tag_string": "mp4a", + "codec_tag": "0x6134706d", + "sample_fmt": "fltp", + "sample_rate": "48000", + "channels": 7, + "bits_per_sample": 0, + "r_frame_rate": "0/0", + "avg_frame_rate": "0/0", + "time_base": "1/48000", + "start_pts": 0, + "start_time": "0.000000", + "duration_ts": 758115312, + "duration": "15794.069000", + "bit_rate": "224197", + "nb_frames": "740348", + "disposition": { + "default": 1, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0 + }, + "tags": { + "creation_time": "2021-09-13T22:42:42.000000Z", + "language": "eng", + "handler_name": "Surround 6.1", + "vendor_id": "[0][0][0][0]" + } + }, + { + "index": 2, + "codec_name": "aac", + "codec_long_name": "AAC (Advanced Audio Coding)", + "profile": "LC", + "codec_type": "audio", + "codec_tag_string": "mp4a", + "codec_tag": "0x6134706d", + "sample_fmt": "fltp", + "sample_rate": "48000", + "channels": 2, + "channel_layout": "stereo", + "bits_per_sample": 0, + "r_frame_rate": "0/0", + "avg_frame_rate": "0/0", + "time_base": "1/48000", + "start_pts": 0, + "start_time": "0.000000", + "duration_ts": 758114304, + "duration": "15794.048000", + "bit_rate": "160519", + "nb_frames": "740347", + "disposition": { + "default": 0, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0 + }, + "tags": { + "creation_time": "2021-09-13T22:42:42.000000Z", + "language": "eng", + "handler_name": "Commentary", + "vendor_id": "[0][0][0][0]" + } + }, + { + "index": 3, + "codec_name": "dvd_subtitle", + "codec_long_name": "DVD subtitles", + "codec_type": "subtitle", + "codec_tag_string": "mp4s", + "codec_tag": "0x7334706d", + "width": 720, + "height": 480, + "r_frame_rate": "0/0", + "avg_frame_rate": "0/0", + "time_base": "1/90000", + "start_pts": 0, + "start_time": "0.000000", + "duration_ts": 1300301588, + "duration": "14447.795422", + "bit_rate": "2653", + "nb_frames": "3545", + "disposition": { + "default": 0, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0 + }, + "tags": { + "creation_time": "2021-09-13T22:42:42.000000Z", + "language": "spa", + "handler_name": "SubtitleHandler" + } + }, + { + "index": 4, + "codec_name": "mov_text", + "codec_long_name": "MOV text", + "codec_type": "subtitle", + "codec_tag_string": "tx3g", + "codec_tag": "0x67337874", + "width": 853, + "height": 51, + "r_frame_rate": "0/0", + "avg_frame_rate": "0/0", + "time_base": "1/90000", + "start_pts": 0, + "start_time": "0.000000", + "duration_ts": 1401339330, + "duration": "15570.437000", + "bit_rate": "88", + "nb_frames": "5079", + "disposition": { + "default": 1, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0 + }, + "tags": { + "creation_time": "2021-09-13T22:42:42.000000Z", + "language": "eng", + "handler_name": "SubtitleHandler" + } + }, + { + "index": 5, + "codec_name": "mov_text", + "codec_long_name": "MOV text", + "codec_type": "subtitle", + "codec_tag_string": "tx3g", + "codec_tag": "0x67337874", + "width": 853, + "height": 51, + "r_frame_rate": "0/0", + "avg_frame_rate": "0/0", + "time_base": "1/90000", + "start_pts": 0, + "start_time": "0.000000", + "duration_ts": 1370580300, + "duration": "15228.670000", + "bit_rate": "18", + "nb_frames": "1563", + "disposition": { + "default": 0, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0 + }, + "tags": { + "creation_time": "2021-09-13T22:42:42.000000Z", + "language": "eng", + "handler_name": "Commentary" + } + } + ] +} From 5043a887cc1a837fae76b7ebd7403750dfef6c7d Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 2 Oct 2021 11:54:31 -0400 Subject: [PATCH 151/278] Document IStartupOptions --- Emby.Server.Implementations/IStartupOptions.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 1d97882db53..3769ae4dd8f 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -1,7 +1,8 @@ -#pragma warning disable CS1591 - namespace Emby.Server.Implementations { + /// + /// Specifies the contract for server startup options. + /// public interface IStartupOptions { /// @@ -10,7 +11,7 @@ namespace Emby.Server.Implementations string? FFmpegPath { get; } /// - /// Gets a value value indicating whether to run as service by the --service command line option. + /// Gets a value indicating whether to run as service by the --service command line option. /// bool IsService { get; } From 4ba9b6c30568c239ab9070734746c03765faac8f Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 2 Oct 2021 12:53:51 -0400 Subject: [PATCH 152/278] Fix warnings in ScheduledTasks --- .../ScheduledTasks/ScheduledTaskWorker.cs | 14 ++++---- .../ScheduledTasks/TaskManager.cs | 26 +++++++-------- .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 6 ++++ .../Tasks/OptimizeDatabaseTask.cs | 3 ++ .../Tasks/PeopleValidationTask.cs | 29 ++++++++-------- .../Tasks/RefreshMediaLibraryTask.cs | 33 +++++++------------ 6 files changed, 56 insertions(+), 55 deletions(-) diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index fb93c375d8d..f2cdfeb16c2 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -10,9 +10,9 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Events; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using Jellyfin.Extensions.Json; using MediaBrowser.Common.Progress; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; @@ -24,6 +24,11 @@ namespace Emby.Server.Implementations.ScheduledTasks /// public class ScheduledTaskWorker : IScheduledTaskWorker { + /// + /// The options for the json Serializer. + /// + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + /// /// Gets or sets the application paths. /// @@ -66,11 +71,6 @@ namespace Emby.Server.Implementations.ScheduledTasks /// private string _id; - /// - /// The options for the json Serializer. - /// - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; - /// /// Initializes a new instance of the class. /// @@ -365,7 +365,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// Task options. /// Task. - /// Cannot execute a Task that is already running + /// Cannot execute a Task that is already running. public async Task Execute(TaskOptions options) { var task = Task.Run(async () => await ExecuteInternal(options).ConfigureAwait(false)); diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index 4f0df75bf83..0431858fcaf 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -19,16 +19,6 @@ namespace Emby.Server.Implementations.ScheduledTasks /// public class TaskManager : ITaskManager { - public event EventHandler> TaskExecuting; - - public event EventHandler TaskCompleted; - - /// - /// Gets the list of Scheduled Tasks. - /// - /// The scheduled tasks. - public IScheduledTaskWorker[] ScheduledTasks { get; private set; } - /// /// The _task queue. /// @@ -53,10 +43,20 @@ namespace Emby.Server.Implementations.ScheduledTasks ScheduledTasks = Array.Empty(); } + public event EventHandler> TaskExecuting; + + public event EventHandler TaskCompleted; + + /// + /// Gets the list of Scheduled Tasks. + /// + /// The scheduled tasks. + public IScheduledTaskWorker[] ScheduledTasks { get; private set; } + /// /// Cancels if running and queue. /// - /// + /// The task type. /// Task options. public void CancelIfRunningAndQueue(TaskOptions options) where T : IScheduledTask @@ -76,7 +76,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// Cancels if running. /// - /// + /// The task type. public void CancelIfRunning() where T : IScheduledTask { @@ -87,7 +87,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// Queues the scheduled task. /// - /// + /// The task type. /// Task options. public void QueueScheduledTask(TaskOptions options) where T : IScheduledTask diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index b764a139cb3..09ea6271d9b 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -39,6 +39,12 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// Initializes a new instance of the class. /// + /// The library manager.. + /// The item repository. + /// The application paths. + /// The encoding manager. + /// The filesystem. + /// The localization manager. public ChapterImagesTask( ILibraryManager libraryManager, IItemRepository itemRepo, diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs index 1ad1d0f50ab..35a4aeef69c 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs @@ -22,6 +22,9 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// /// Initializes a new instance of the class. /// + /// The logger. + /// The localization manager. + /// The jellyfin DB context provider. public OptimizeDatabaseTask( ILogger logger, ILocalizationManager localization, diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs index 57d294a4088..53c692a4610 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs @@ -32,9 +32,24 @@ namespace Emby.Server.Implementations.ScheduledTasks _localization = localization; } + public string Name => _localization.GetLocalizedString("TaskRefreshPeople"); + + public string Description => _localization.GetLocalizedString("TaskRefreshPeopleDescription"); + + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + + public string Key => "RefreshPeople"; + + public bool IsHidden => false; + + public bool IsEnabled => true; + + public bool IsLogged => true; + /// /// Creates the triggers that define when the task will run. /// + /// An containing the default trigger infos for this task. public IEnumerable GetDefaultTriggers() { return new[] @@ -57,19 +72,5 @@ namespace Emby.Server.Implementations.ScheduledTasks { return _libraryManager.ValidatePeople(cancellationToken, progress); } - - public string Name => _localization.GetLocalizedString("TaskRefreshPeople"); - - public string Description => _localization.GetLocalizedString("TaskRefreshPeopleDescription"); - - public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); - - public string Key => "RefreshPeople"; - - public bool IsHidden => false; - - public bool IsEnabled => true; - - public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs index 51b620404b9..2184b3d033e 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs @@ -33,6 +33,18 @@ namespace Emby.Server.Implementations.ScheduledTasks _localization = localization; } + /// + public string Name => _localization.GetLocalizedString("TaskRefreshLibrary"); + + /// + public string Description => _localization.GetLocalizedString("TaskRefreshLibraryDescription"); + + /// + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + + /// + public string Key => "RefreshLibrary"; + /// /// Creates the triggers that define when the task will run. /// @@ -60,26 +72,5 @@ namespace Emby.Server.Implementations.ScheduledTasks return ((LibraryManager)_libraryManager).ValidateMediaLibraryInternal(progress, cancellationToken); } - - /// - public string Name => _localization.GetLocalizedString("TaskRefreshLibrary"); - - /// - public string Description => _localization.GetLocalizedString("TaskRefreshLibraryDescription"); - - /// - public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); - - /// - public string Key => "RefreshLibrary"; - - /// - public bool IsHidden => false; - - /// - public bool IsEnabled => true; - - /// - public bool IsLogged => true; } } From 21007aec2038b88734ae19287081bcb1b04f6300 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 2 Oct 2021 13:03:04 -0400 Subject: [PATCH 153/278] Fix warnings in Data --- .../Data/BaseSqliteRepository.cs | 51 -- .../Data/ManagedConnection.cs | 4 +- .../Data/SqliteItemRepository.cs | 481 +++++++++--------- .../Data/SqliteUserDataRepository.cs | 7 +- .../Data/SynchronouseMode.cs | 30 ++ .../Data/TempStoreMode.cs | 23 + 6 files changed, 302 insertions(+), 294 deletions(-) create mode 100644 Emby.Server.Implementations/Data/SynchronouseMode.cs create mode 100644 Emby.Server.Implementations/Data/TempStoreMode.cs diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 01c9fbca815..4f6c811028f 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -249,55 +249,4 @@ namespace Emby.Server.Implementations.Data _disposed = true; } } - - /// - /// The disk synchronization mode, controls how aggressively SQLite will write data - /// all the way out to physical storage. - /// - public enum SynchronousMode - { - /// - /// SQLite continues without syncing as soon as it has handed data off to the operating system. - /// - Off = 0, - - /// - /// SQLite database engine will still sync at the most critical moments. - /// - Normal = 1, - - /// - /// SQLite database engine will use the xSync method of the VFS - /// to ensure that all content is safely written to the disk surface prior to continuing. - /// - Full = 2, - - /// - /// EXTRA synchronous is like FULL with the addition that the directory containing a rollback journal - /// is synced after that journal is unlinked to commit a transaction in DELETE mode. - /// - Extra = 3 - } - - /// - /// Storage mode used by temporary database files. - /// - public enum TempStoreMode - { - /// - /// The compile-time C preprocessor macro SQLITE_TEMP_STORE - /// is used to determine where temporary tables and indices are stored. - /// - Default = 0, - - /// - /// Temporary tables and indices are stored in a file. - /// - File = 1, - - /// - /// Temporary tables and indices are kept in as if they were pure in-memory databases memory. - /// - Memory = 2 - } } diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs index afc8966f9cd..44dad5b1786 100644 --- a/Emby.Server.Implementations/Data/ManagedConnection.cs +++ b/Emby.Server.Implementations/Data/ManagedConnection.cs @@ -9,8 +9,10 @@ namespace Emby.Server.Implementations.Data { public class ManagedConnection : IDisposable { - private SQLiteDatabaseConnection? _db; private readonly SemaphoreSlim _writeLock; + + private SQLiteDatabaseConnection? _db; + private bool _disposed = false; public ManagedConnection(SQLiteDatabaseConnection db, SemaphoreSlim writeLock) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 0a48b844dd1..13f1df7c863 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -46,6 +46,11 @@ namespace Emby.Server.Implementations.Data private const string FromText = " from TypedBaseItems A"; private const string ChaptersTableName = "Chapters2"; + private const string SaveItemCommandText = + @"replace into TypedBaseItems + (guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId) + values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId)"; + private readonly IServerConfigurationManager _config; private readonly IServerApplicationHost _appHost; private readonly ILocalizationManager _localization; @@ -55,6 +60,231 @@ namespace Emby.Server.Implementations.Data private readonly TypeMapper _typeMapper; private readonly JsonSerializerOptions _jsonOptions; + private readonly ItemFields[] _allItemFields = Enum.GetValues(); + + private static readonly string[] _retriveItemColumns = + { + "type", + "data", + "StartDate", + "EndDate", + "ChannelId", + "IsMovie", + "IsSeries", + "EpisodeTitle", + "IsRepeat", + "CommunityRating", + "CustomRating", + "IndexNumber", + "IsLocked", + "PreferredMetadataLanguage", + "PreferredMetadataCountryCode", + "Width", + "Height", + "DateLastRefreshed", + "Name", + "Path", + "PremiereDate", + "Overview", + "ParentIndexNumber", + "ProductionYear", + "OfficialRating", + "ForcedSortName", + "RunTimeTicks", + "Size", + "DateCreated", + "DateModified", + "guid", + "Genres", + "ParentId", + "Audio", + "ExternalServiceId", + "IsInMixedFolder", + "DateLastSaved", + "LockedFields", + "Studios", + "Tags", + "TrailerTypes", + "OriginalTitle", + "PrimaryVersionId", + "DateLastMediaAdded", + "Album", + "CriticRating", + "IsVirtualItem", + "SeriesName", + "SeasonName", + "SeasonId", + "SeriesId", + "PresentationUniqueKey", + "InheritedParentalRatingValue", + "ExternalSeriesId", + "Tagline", + "ProviderIds", + "Images", + "ProductionLocations", + "ExtraIds", + "TotalBitrate", + "ExtraType", + "Artists", + "AlbumArtists", + "ExternalId", + "SeriesPresentationUniqueKey", + "ShowId", + "OwnerId" + }; + + private static readonly string _retriveItemColumnsSelectQuery = $"select {string.Join(',', _retriveItemColumns)} from TypedBaseItems where guid = @guid"; + + private static readonly string[] _mediaStreamSaveColumns = + { + "ItemId", + "StreamIndex", + "StreamType", + "Codec", + "Language", + "ChannelLayout", + "Profile", + "AspectRatio", + "Path", + "IsInterlaced", + "BitRate", + "Channels", + "SampleRate", + "IsDefault", + "IsForced", + "IsExternal", + "Height", + "Width", + "AverageFrameRate", + "RealFrameRate", + "Level", + "PixelFormat", + "BitDepth", + "IsAnamorphic", + "RefFrames", + "CodecTag", + "Comment", + "NalLengthSize", + "IsAvc", + "Title", + "TimeBase", + "CodecTimeBase", + "ColorPrimaries", + "ColorSpace", + "ColorTransfer" + }; + + private static readonly string _mediaStreamSaveColumnsInsertQuery = + $"insert into mediastreams ({string.Join(',', _mediaStreamSaveColumns)}) values "; + + private static readonly string _mediaStreamSaveColumnsSelectQuery = + $"select {string.Join(',', _mediaStreamSaveColumns)} from mediastreams where ItemId=@ItemId"; + + private static readonly string[] _mediaAttachmentSaveColumns = + { + "ItemId", + "AttachmentIndex", + "Codec", + "CodecTag", + "Comment", + "Filename", + "MIMEType" + }; + + private static readonly string _mediaAttachmentSaveColumnsSelectQuery = + $"select {string.Join(',', _mediaAttachmentSaveColumns)} from mediaattachments where ItemId=@ItemId"; + + private static readonly string _mediaAttachmentInsertPrefix; + + private static readonly HashSet _programTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Program", + "TvChannel", + "LiveTvProgram", + "LiveTvTvChannel" + }; + + private static readonly HashSet _programExcludeParentTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Series", + "Season", + "MusicAlbum", + "MusicArtist", + "PhotoAlbum" + }; + + private static readonly HashSet _serviceTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "TvChannel", + "LiveTvTvChannel" + }; + + private static readonly HashSet _startDateTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Program", + "LiveTvProgram" + }; + + private static readonly HashSet _seriesTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Book", + "AudioBook", + "Episode", + "Season" + }; + + private static readonly HashSet _artistExcludeParentTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Series", + "Season", + "PhotoAlbum" + }; + + private static readonly HashSet _artistsTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Audio", + "MusicAlbum", + "MusicVideo", + "AudioBook", + "AudioPodcast" + }; + + private static readonly Type[] _knownTypes = + { + typeof(LiveTvProgram), + typeof(LiveTvChannel), + typeof(Series), + typeof(Audio), + typeof(MusicAlbum), + typeof(MusicArtist), + typeof(MusicGenre), + typeof(MusicVideo), + typeof(Movie), + typeof(Playlist), + typeof(AudioBook), + typeof(Trailer), + typeof(BoxSet), + typeof(Episode), + typeof(Season), + typeof(Series), + typeof(Book), + typeof(CollectionFolder), + typeof(Folder), + typeof(Genre), + typeof(Person), + typeof(Photo), + typeof(PhotoAlbum), + typeof(Studio), + typeof(UserRootFolder), + typeof(UserView), + typeof(Video), + typeof(Year), + typeof(Channel), + typeof(AggregateFolder) + }; + + private readonly Dictionary _types = GetTypeMapDictionary(); + static SqliteItemRepository() { var queryPrefixText = new StringBuilder(); @@ -115,6 +345,8 @@ namespace Emby.Server.Implementations.Data /// /// Opens the connection to the database. /// + /// The user data repository. + /// The user manager. public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager) { const string CreateMediaStreamsTableCommand @@ -154,7 +386,7 @@ namespace Emby.Server.Implementations.Data "drop index if exists idx_TypedBaseItems", "drop index if exists idx_mediastreams", "drop index if exists idx_mediastreams1", - "drop index if exists idx_"+ChaptersTableName, + "drop index if exists idx_" + ChaptersTableName, "drop index if exists idx_UserDataKeys1", "drop index if exists idx_UserDataKeys2", "drop index if exists idx_TypeTopParentId3", @@ -340,151 +572,12 @@ namespace Emby.Server.Implementations.Data userDataRepo.Initialize(userManager, WriteLock, WriteConnection); } - private static readonly string[] _retriveItemColumns = - { - "type", - "data", - "StartDate", - "EndDate", - "ChannelId", - "IsMovie", - "IsSeries", - "EpisodeTitle", - "IsRepeat", - "CommunityRating", - "CustomRating", - "IndexNumber", - "IsLocked", - "PreferredMetadataLanguage", - "PreferredMetadataCountryCode", - "Width", - "Height", - "DateLastRefreshed", - "Name", - "Path", - "PremiereDate", - "Overview", - "ParentIndexNumber", - "ProductionYear", - "OfficialRating", - "ForcedSortName", - "RunTimeTicks", - "Size", - "DateCreated", - "DateModified", - "guid", - "Genres", - "ParentId", - "Audio", - "ExternalServiceId", - "IsInMixedFolder", - "DateLastSaved", - "LockedFields", - "Studios", - "Tags", - "TrailerTypes", - "OriginalTitle", - "PrimaryVersionId", - "DateLastMediaAdded", - "Album", - "CriticRating", - "IsVirtualItem", - "SeriesName", - "SeasonName", - "SeasonId", - "SeriesId", - "PresentationUniqueKey", - "InheritedParentalRatingValue", - "ExternalSeriesId", - "Tagline", - "ProviderIds", - "Images", - "ProductionLocations", - "ExtraIds", - "TotalBitrate", - "ExtraType", - "Artists", - "AlbumArtists", - "ExternalId", - "SeriesPresentationUniqueKey", - "ShowId", - "OwnerId" - }; - - private static readonly string _retriveItemColumnsSelectQuery = $"select {string.Join(',', _retriveItemColumns)} from TypedBaseItems where guid = @guid"; - - private static readonly string[] _mediaStreamSaveColumns = - { - "ItemId", - "StreamIndex", - "StreamType", - "Codec", - "Language", - "ChannelLayout", - "Profile", - "AspectRatio", - "Path", - "IsInterlaced", - "BitRate", - "Channels", - "SampleRate", - "IsDefault", - "IsForced", - "IsExternal", - "Height", - "Width", - "AverageFrameRate", - "RealFrameRate", - "Level", - "PixelFormat", - "BitDepth", - "IsAnamorphic", - "RefFrames", - "CodecTag", - "Comment", - "NalLengthSize", - "IsAvc", - "Title", - "TimeBase", - "CodecTimeBase", - "ColorPrimaries", - "ColorSpace", - "ColorTransfer" - }; - - private static readonly string _mediaStreamSaveColumnsInsertQuery = - $"insert into mediastreams ({string.Join(',', _mediaStreamSaveColumns)}) values "; - - private static readonly string _mediaStreamSaveColumnsSelectQuery = - $"select {string.Join(',', _mediaStreamSaveColumns)} from mediastreams where ItemId=@ItemId"; - - private static readonly string[] _mediaAttachmentSaveColumns = - { - "ItemId", - "AttachmentIndex", - "Codec", - "CodecTag", - "Comment", - "Filename", - "MIMEType" - }; - - private static readonly string _mediaAttachmentSaveColumnsSelectQuery = - $"select {string.Join(',', _mediaAttachmentSaveColumns)} from mediaattachments where ItemId=@ItemId"; - - private static readonly string _mediaAttachmentInsertPrefix; - - private const string SaveItemCommandText = - @"replace into TypedBaseItems - (guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId) - values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId)"; - /// /// Save a standard item in the repo. /// /// The item. /// The cancellation token. - /// item + /// is null. public void SaveItem(BaseItem item, CancellationToken cancellationToken) { if (item == null) @@ -509,7 +602,7 @@ namespace Emby.Server.Implementations.Data connection.RunInTransaction( db => { - using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id")) + using (var saveImagesStatement = PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id")) { saveImagesStatement.TryBind("@Id", item.Id.ToByteArray()); saveImagesStatement.TryBind("@Images", SerializeImages(item.ImageInfos)); @@ -526,9 +619,7 @@ namespace Emby.Server.Implementations.Data /// The items. /// The cancellation token. /// - /// items - /// or - /// cancellationToken + /// or is null. /// public void SaveItems(IEnumerable items, CancellationToken cancellationToken) { @@ -1216,8 +1307,8 @@ namespace Emby.Server.Implementations.Data /// /// The id. /// BaseItem. - /// id - /// + /// is null. + /// is . public BaseItem RetrieveItem(Guid id) { if (id == Guid.Empty) @@ -1986,6 +2077,8 @@ namespace Emby.Server.Implementations.Data /// /// Saves the chapters. /// + /// The item id. + /// The chapters. public void SaveChapters(Guid id, IReadOnlyList chapters) { CheckDisposed(); @@ -2085,8 +2178,6 @@ namespace Emby.Server.Implementations.Data || query.IsLiked.HasValue; } - private readonly ItemFields[] _allFields = Enum.GetValues(); - private bool HasField(InternalItemsQuery query, ItemFields name) { switch (name) @@ -2119,23 +2210,6 @@ namespace Emby.Server.Implementations.Data } } - private static readonly HashSet _programExcludeParentTypes = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "Series", - "Season", - "MusicAlbum", - "MusicArtist", - "PhotoAlbum" - }; - - private static readonly HashSet _programTypes = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "Program", - "TvChannel", - "LiveTvProgram", - "LiveTvTvChannel" - }; - private bool HasProgramAttributes(InternalItemsQuery query) { if (_programExcludeParentTypes.Contains(query.ParentType)) @@ -2151,12 +2225,6 @@ namespace Emby.Server.Implementations.Data return query.IncludeItemTypes.Any(x => _programTypes.Contains(x)); } - private static readonly HashSet _serviceTypes = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "TvChannel", - "LiveTvTvChannel" - }; - private bool HasServiceName(InternalItemsQuery query) { if (_programExcludeParentTypes.Contains(query.ParentType)) @@ -2172,12 +2240,6 @@ namespace Emby.Server.Implementations.Data return query.IncludeItemTypes.Any(x => _serviceTypes.Contains(x)); } - private static readonly HashSet _startDateTypes = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "Program", - "LiveTvProgram" - }; - private bool HasStartDate(InternalItemsQuery query) { if (_programExcludeParentTypes.Contains(query.ParentType)) @@ -2213,22 +2275,6 @@ namespace Emby.Server.Implementations.Data return query.IncludeItemTypes.Contains("Trailer", StringComparer.OrdinalIgnoreCase); } - private static readonly HashSet _artistExcludeParentTypes = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "Series", - "Season", - "PhotoAlbum" - }; - - private static readonly HashSet _artistsTypes = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "Audio", - "MusicAlbum", - "MusicVideo", - "AudioBook", - "AudioPodcast" - }; - private bool HasArtistFields(InternalItemsQuery query) { if (_artistExcludeParentTypes.Contains(query.ParentType)) @@ -2244,14 +2290,6 @@ namespace Emby.Server.Implementations.Data return query.IncludeItemTypes.Any(x => _artistsTypes.Contains(x)); } - private static readonly HashSet _seriesTypes = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "Book", - "AudioBook", - "Episode", - "Season" - }; - private bool HasSeriesFields(InternalItemsQuery query) { if (string.Equals(query.ParentType, "PhotoAlbum", StringComparison.OrdinalIgnoreCase)) @@ -2269,7 +2307,7 @@ namespace Emby.Server.Implementations.Data private void SetFinalColumnsToSelect(InternalItemsQuery query, List columns) { - foreach (var field in _allFields) + foreach (var field in _allItemFields) { if (!HasField(query, field)) { @@ -4811,40 +4849,6 @@ namespace Emby.Server.Implementations.Data return false; } - private static readonly Type[] _knownTypes = - { - typeof(LiveTvProgram), - typeof(LiveTvChannel), - typeof(Series), - typeof(Audio), - typeof(MusicAlbum), - typeof(MusicArtist), - typeof(MusicGenre), - typeof(MusicVideo), - typeof(Movie), - typeof(Playlist), - typeof(AudioBook), - typeof(Trailer), - typeof(BoxSet), - typeof(Episode), - typeof(Season), - typeof(Series), - typeof(Book), - typeof(CollectionFolder), - typeof(Folder), - typeof(Genre), - typeof(Person), - typeof(Photo), - typeof(PhotoAlbum), - typeof(Studio), - typeof(UserRootFolder), - typeof(UserView), - typeof(Video), - typeof(Year), - typeof(Channel), - typeof(AggregateFolder) - }; - public void UpdateInheritedValues() { string sql = string.Join( @@ -4886,9 +4890,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return dict; } - // Not crazy about having this all the way down here, but at least it's in one place - private readonly Dictionary _types = GetTypeMapDictionary(); - private string MapIncludeItemTypes(string value) { if (_types.TryGetValue(value, out string result)) diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 829f1de2f6d..107096b5f2e 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -32,6 +32,9 @@ namespace Emby.Server.Implementations.Data /// /// Opens the connection to the database. /// + /// The user manager. + /// The lock to use for database IO. + /// The connection to use for database IO. public void Initialize(IUserManager userManager, SemaphoreSlim dbLock, SQLiteDatabaseConnection dbConnection) { WriteLock.Dispose(); @@ -49,8 +52,8 @@ namespace Emby.Server.Implementations.Data connection.RunInTransaction( db => { - db.ExecuteAll(string.Join(';', new[] { - + db.ExecuteAll(string.Join(';', new[] + { "create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)", "drop index if exists idx_userdata", diff --git a/Emby.Server.Implementations/Data/SynchronouseMode.cs b/Emby.Server.Implementations/Data/SynchronouseMode.cs new file mode 100644 index 00000000000..cde524e2e00 --- /dev/null +++ b/Emby.Server.Implementations/Data/SynchronouseMode.cs @@ -0,0 +1,30 @@ +namespace Emby.Server.Implementations.Data; + +/// +/// The disk synchronization mode, controls how aggressively SQLite will write data +/// all the way out to physical storage. +/// +public enum SynchronousMode +{ + /// + /// SQLite continues without syncing as soon as it has handed data off to the operating system. + /// + Off = 0, + + /// + /// SQLite database engine will still sync at the most critical moments. + /// + Normal = 1, + + /// + /// SQLite database engine will use the xSync method of the VFS + /// to ensure that all content is safely written to the disk surface prior to continuing. + /// + Full = 2, + + /// + /// EXTRA synchronous is like FULL with the addition that the directory containing a rollback journal + /// is synced after that journal is unlinked to commit a transaction in DELETE mode. + /// + Extra = 3 +} diff --git a/Emby.Server.Implementations/Data/TempStoreMode.cs b/Emby.Server.Implementations/Data/TempStoreMode.cs new file mode 100644 index 00000000000..d2427ce4780 --- /dev/null +++ b/Emby.Server.Implementations/Data/TempStoreMode.cs @@ -0,0 +1,23 @@ +namespace Emby.Server.Implementations.Data; + +/// +/// Storage mode used by temporary database files. +/// +public enum TempStoreMode +{ + /// + /// The compile-time C preprocessor macro SQLITE_TEMP_STORE + /// is used to determine where temporary tables and indices are stored. + /// + Default = 0, + + /// + /// Temporary tables and indices are stored in a file. + /// + File = 1, + + /// + /// Temporary tables and indices are kept in as if they were pure in-memory databases memory. + /// + Memory = 2 +} From 7c282ec3694d8e6a127588c41cd0c68f8ee4e93b Mon Sep 17 00:00:00 2001 From: KonH Date: Sun, 3 Oct 2021 10:43:05 +0700 Subject: [PATCH 154/278] Fix warning: The nullable warning suppression expression is redundant (#2149) --- .../SyncPlayAccessPolicy/SyncPlayAccessHandler.cs | 4 ++-- Jellyfin.Api/Controllers/DashboardController.cs | 2 +- Jellyfin.Api/Controllers/DynamicHlsController.cs | 2 +- Jellyfin.Api/Controllers/HlsSegmentController.cs | 4 ++-- Jellyfin.Api/Controllers/InstantMixController.cs | 14 +++++++------- Jellyfin.Api/Controllers/ItemsController.cs | 2 +- Jellyfin.Api/Controllers/MediaInfoController.cs | 2 +- Jellyfin.Api/Controllers/TvShowsController.cs | 8 ++++---- .../Controllers/UniversalAudioController.cs | 2 +- Jellyfin.Api/Controllers/VideosController.cs | 2 +- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 8 ++++---- Jellyfin.Api/Models/StreamingDtos/StreamState.cs | 2 +- .../Security/AuthorizationContext.cs | 2 +- 13 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs index b898ac76c8f..e6c04eb082f 100644 --- a/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs +++ b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs @@ -51,7 +51,7 @@ namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy { if (user.SyncPlayAccess == SyncPlayUserAccessType.CreateAndJoinGroups || user.SyncPlayAccess == SyncPlayUserAccessType.JoinGroups - || _syncPlayManager.IsUserActive(userId!.Value)) + || _syncPlayManager.IsUserActive(userId.Value)) { context.Succeed(requirement); } @@ -85,7 +85,7 @@ namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy } else if (requirement.RequiredAccess == SyncPlayAccessRequirementType.IsInGroup) { - if (_syncPlayManager.IsUserActive(userId!.Value)) + if (_syncPlayManager.IsUserActive(userId.Value)) { context.Succeed(requirement); } diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs index 445733c24d2..87cb418d971 100644 --- a/Jellyfin.Api/Controllers/DashboardController.cs +++ b/Jellyfin.Api/Controllers/DashboardController.cs @@ -53,7 +53,7 @@ namespace Jellyfin.Api.Controllers if (enableInMainMenu.HasValue) { - configPages = configPages.Where(p => p!.EnableInMainMenu == enableInMainMenu.Value).ToList(); + configPages = configPages.Where(p => p.EnableInMainMenu == enableInMainMenu.Value).ToList(); } return configPages; diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index a540033579b..42e82dd5b16 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1709,7 +1709,7 @@ namespace Jellyfin.Api.Controllers return Task.CompletedTask; }); - return FileStreamResponseHelpers.GetStaticFileResult(segmentPath, MimeTypes.GetMimeType(segmentPath)!, false, HttpContext); + return FileStreamResponseHelpers.GetStaticFileResult(segmentPath, MimeTypes.GetMimeType(segmentPath), false, HttpContext); } private long GetEndPositionTicks(StreamState state, int requestedIndex) diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs index 473bdc523c9..71caa0fe0de 100644 --- a/Jellyfin.Api/Controllers/HlsSegmentController.cs +++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs @@ -69,7 +69,7 @@ namespace Jellyfin.Api.Controllers return BadRequest("Invalid segment."); } - return FileStreamResponseHelpers.GetStaticFileResult(file, MimeTypes.GetMimeType(file)!, false, HttpContext); + return FileStreamResponseHelpers.GetStaticFileResult(file, MimeTypes.GetMimeType(file), false, HttpContext); } /// @@ -186,7 +186,7 @@ namespace Jellyfin.Api.Controllers return Task.CompletedTask; }); - return FileStreamResponseHelpers.GetStaticFileResult(path, MimeTypes.GetMimeType(path)!, false, HttpContext); + return FileStreamResponseHelpers.GetStaticFileResult(path, MimeTypes.GetMimeType(path), false, HttpContext); } } } diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index 4774ed4efaa..a6c2e07c939 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -80,7 +80,7 @@ namespace Jellyfin.Api.Controllers : null; var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) - .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); return GetResult(items, user, limit, dtoOptions); } @@ -116,7 +116,7 @@ namespace Jellyfin.Api.Controllers : null; var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) - .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); var items = _musicManager.GetInstantMixFromItem(album, user, dtoOptions); return GetResult(items, user, limit, dtoOptions); } @@ -152,7 +152,7 @@ namespace Jellyfin.Api.Controllers : null; var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) - .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); var items = _musicManager.GetInstantMixFromItem(playlist, user, dtoOptions); return GetResult(items, user, limit, dtoOptions); } @@ -187,7 +187,7 @@ namespace Jellyfin.Api.Controllers : null; var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) - .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); var items = _musicManager.GetInstantMixFromGenres(new[] { name }, user, dtoOptions); return GetResult(items, user, limit, dtoOptions); } @@ -223,7 +223,7 @@ namespace Jellyfin.Api.Controllers : null; var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) - .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); return GetResult(items, user, limit, dtoOptions); } @@ -259,7 +259,7 @@ namespace Jellyfin.Api.Controllers : null; var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) - .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); return GetResult(items, user, limit, dtoOptions); } @@ -332,7 +332,7 @@ namespace Jellyfin.Api.Controllers : null; var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) - .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); return GetResult(items, user, limit, dtoOptions); } diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 52eefc5c231..f0d44e5cc82 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -287,7 +287,7 @@ namespace Jellyfin.Api.Controllers if ((recursive.HasValue && recursive.Value) || ids.Length != 0 || item is not UserRootFolder) { - var query = new InternalItemsQuery(user!) + var query = new InternalItemsQuery(user) { IsPlayed = isPlayed, MediaTypes = mediaTypes, diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs index 7c78928f729..96ef2d678b2 100644 --- a/Jellyfin.Api/Controllers/MediaInfoController.cs +++ b/Jellyfin.Api/Controllers/MediaInfoController.cs @@ -184,7 +184,7 @@ namespace Jellyfin.Api.Controllers audioStreamIndex, subtitleStreamIndex, maxAudioChannels, - info!.PlaySessionId!, + info.PlaySessionId!, userId ?? Guid.Empty, enableDirectPlay.Value, enableDirectStream.Value, diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 6eada67cf06..5dd7733316f 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers { var options = new DtoOptions { Fields = fields } .AddClientFields(Request) - .AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes!); + .AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes); var result = _tvSeriesManager.GetNextUp( new NextUpQuery @@ -153,7 +153,7 @@ namespace Jellyfin.Api.Controllers var options = new DtoOptions { Fields = fields } .AddClientFields(Request) - .AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes!); + .AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes); var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) { @@ -223,7 +223,7 @@ namespace Jellyfin.Api.Controllers var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) - .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); if (seasonId.HasValue) // Season id was supplied. Get episodes by season id. { @@ -350,7 +350,7 @@ namespace Jellyfin.Api.Controllers var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) - .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!); + .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); var returnItems = _dtoService.GetBaseItemDtos(seasons, dtoOptions, user); diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 20a02bf4a9d..bc9527a0bc4 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -155,7 +155,7 @@ namespace Jellyfin.Api.Controllers null, null, maxAudioChannels, - info!.PlaySessionId!, + info.PlaySessionId!, userId ?? Guid.Empty, true, true, diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 150f22d1b8e..e1cbc6f3314 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -461,7 +461,7 @@ namespace Jellyfin.Api.Controllers var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream()); // TODO (moved from MediaBrowser.Api): Don't hardcode contentType - return File(liveStream, MimeTypes.GetMimeType("file.ts")!); + return File(liveStream, MimeTypes.GetMimeType("file.ts")); } // Static remote stream diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 14f287aef68..488856c4ea4 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -86,8 +86,8 @@ namespace Jellyfin.Api.Helpers DeleteEncodedMediaCache(); - sessionManager!.PlaybackProgress += OnPlaybackProgress; - sessionManager!.PlaybackStart += OnPlaybackProgress; + sessionManager.PlaybackProgress += OnPlaybackProgress; + sessionManager.PlaybackStart += OnPlaybackProgress; } /// @@ -878,8 +878,8 @@ namespace Jellyfin.Api.Helpers if (disposing) { _loggerFactory.Dispose(); - _sessionManager!.PlaybackProgress -= OnPlaybackProgress; - _sessionManager!.PlaybackStart -= OnPlaybackProgress; + _sessionManager.PlaybackProgress -= OnPlaybackProgress; + _sessionManager.PlaybackStart -= OnPlaybackProgress; } } } diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs index 0f84faeafc3..cbabf087bcb 100644 --- a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs +++ b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs @@ -55,7 +55,7 @@ namespace Jellyfin.Api.Models.StreamingDtos /// /// Gets the video request. /// - public VideoRequestDto? VideoRequest => Request! as VideoRequestDto; + public VideoRequestDto? VideoRequest => Request as VideoRequestDto; /// /// Gets or sets the direct stream provicer. diff --git a/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs index 244abf469ee..ba2cfc724ea 100644 --- a/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs +++ b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs @@ -27,7 +27,7 @@ namespace Jellyfin.Server.Implementations.Security { if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached) && cached != null) { - return Task.FromResult((AuthorizationInfo)cached!); // Cache should never contain null + return Task.FromResult((AuthorizationInfo)cached); // Cache should never contain null } return GetAuthorization(requestContext); From b6bf43af4590579a1902552ed1b1eaaa4946f7b3 Mon Sep 17 00:00:00 2001 From: KonH Date: Sun, 3 Oct 2021 10:49:41 +0700 Subject: [PATCH 155/278] Fix warning: Using directive is not required by the code and can be safely removed (#2149) --- .../SymlinkFollowingPhysicalFileResultExecutor.cs | 1 - Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs | 1 - .../Globalization/ILocalizationManager.cs | 1 - MediaBrowser.Model/IO/AsyncFile.cs | 1 - .../Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs | 1 - .../Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs | 1 - .../Controllers/DynamicHlsControllerTests.cs | 1 - .../EncoderValidatorTests.cs | 2 -- .../Subtitles/SubtitleEncoderTests.cs | 12 ------------ .../Entities/MediaStreamTests.cs | 1 - .../AudioBook/AudioBookResolverTests.cs | 1 - .../Video/VideoResolverTests.cs | 1 - .../LiveTv/RecordingHelperTests.cs | 1 - .../Sorting/AiredEpisodeOrderComparerTests.cs | 2 -- 14 files changed, 27 deletions(-) diff --git a/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs b/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs index be4926da63f..73a619b8d94 100644 --- a/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs +++ b/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs @@ -26,7 +26,6 @@ using System; using System.IO; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.IO; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; diff --git a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs index b5f515cdab4..e4d2937e7ef 100644 --- a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs +++ b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Web; using Jellyfin.Extensions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs index 406d32cde15..e00157dce9a 100644 --- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs +++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Globalization; using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.Globalization diff --git a/MediaBrowser.Model/IO/AsyncFile.cs b/MediaBrowser.Model/IO/AsyncFile.cs index f38ed9ae3d4..caee9c52899 100644 --- a/MediaBrowser.Model/IO/AsyncFile.cs +++ b/MediaBrowser.Model/IO/AsyncFile.cs @@ -1,4 +1,3 @@ -using System; using System.IO; namespace MediaBrowser.Model.IO diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs index a5287e749f3..fc6af0b34e1 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs @@ -13,7 +13,6 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs index 1bda1a09be0..ca44c9bbc31 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs @@ -13,7 +13,6 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.TV diff --git a/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs index 4f413d9652b..1f06e8fde61 100644 --- a/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs +++ b/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Jellyfin.Api.Controllers; using Xunit; diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs index ce1ed86fa6f..c0c363d3da0 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs @@ -1,6 +1,4 @@ using System; -using System.Collections; -using System.Collections.Generic; using MediaBrowser.MediaEncoding.Encoder; using Microsoft.Extensions.Logging.Abstractions; using Xunit; diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs index 5fe2c844716..639c364df2c 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs @@ -1,23 +1,11 @@ -using System; -using System.Globalization; -using System.IO; using System.Threading; using System.Threading.Tasks; using AutoFixture; using AutoFixture.AutoMoq; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.MediaEncoding.Subtitles; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; using Xunit; namespace Jellyfin.MediaEncoding.Subtitles.Tests diff --git a/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs b/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs index 7017b58b975..0c97a90b43f 100644 --- a/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs +++ b/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using MediaBrowser.Model.Entities; using Xunit; diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs index 664136d8d25..c72a3315e7a 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using Emby.Naming.AudioBook; using Emby.Naming.Common; using Xunit; diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs index 420147dcb63..33a99e107fe 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Linq; using Emby.Naming.Common; using Emby.Naming.Video; diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs index bc16e149879..976afe1958c 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Emby.Server.Implementations.LiveTv.EmbyTV; using MediaBrowser.Controller.LiveTv; using Xunit; diff --git a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs index f12681b5932..59d82678e4f 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs @@ -1,6 +1,4 @@ using System; -using System.Collections; -using System.Collections.Generic; using Emby.Server.Implementations.Sorting; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; From e3fccd5ae67b44be1fa44e82fb0954f29cc825ef Mon Sep 17 00:00:00 2001 From: KonH Date: Sun, 3 Oct 2021 11:00:45 +0700 Subject: [PATCH 156/278] Fix warning: Qualifier is redundant (#2149) --- Jellyfin.Api/Helpers/AudioHelper.cs | 2 +- Jellyfin.Server/Program.cs | 6 +++--- MediaBrowser.Controller/Entities/BaseItem.cs | 2 +- MediaBrowser.Controller/Entities/BaseItemExtensions.cs | 2 +- MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs | 2 +- RSSDP/SsdpCommunicationsServer.cs | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs index a5e47b8eca7..bec961dadb8 100644 --- a/Jellyfin.Api/Helpers/AudioHelper.cs +++ b/Jellyfin.Api/Helpers/AudioHelper.cs @@ -147,7 +147,7 @@ namespace Jellyfin.Api.Helpers } var outputPath = state.OutputFilePath; - var outputPathExists = System.IO.File.Exists(outputPath); + var outputPathExists = File.Exists(outputPath); var transcodingJob = _transcodingJobHelper.GetTranscodingJob(outputPath, TranscodingJobType.Progressive); var isTranscodeCached = outputPathExists && transcodingJob != null; diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index f36675b9550..45699f3aff2 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -594,7 +594,7 @@ namespace Jellyfin.Server try { // Serilog.Log is used by SerilogLoggerFactory when no logger is specified - Serilog.Log.Logger = new LoggerConfiguration() + Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(configuration) .Enrich.FromLogContext() .Enrich.WithThreadId() @@ -602,7 +602,7 @@ namespace Jellyfin.Server } catch (Exception ex) { - Serilog.Log.Logger = new LoggerConfiguration() + Log.Logger = new LoggerConfiguration() .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}") .WriteTo.Async(x => x.File( Path.Combine(appPaths.LogDirectoryPath, "log_.log"), @@ -613,7 +613,7 @@ namespace Jellyfin.Server .Enrich.WithThreadId() .CreateLogger(); - Serilog.Log.Logger.Fatal(ex, "Failed to create/read logger configuration"); + Log.Logger.Fatal(ex, "Failed to create/read logger configuration"); } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 1237268d7c9..838a9f2f8d6 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2997,7 +2997,7 @@ namespace MediaBrowser.Controller.Entities } /// - public bool Equals(BaseItem other) => object.Equals(Id, other?.Id); + public bool Equals(BaseItem other) => Equals(Id, other?.Id); /// public override int GetHashCode() => HashCode.Combine(Id); diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs index e88121212ac..33870e2fbfd 100644 --- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs +++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs @@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.Entities /// The file. public static void SetImagePath(this BaseItem item, ImageType imageType, string file) { - if (file.StartsWith("http", System.StringComparison.OrdinalIgnoreCase)) + if (file.StartsWith("http", StringComparison.OrdinalIgnoreCase)) { item.SetImage( new ItemImageInfo diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs index e86e518be64..409379c3559 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs @@ -75,7 +75,7 @@ namespace MediaBrowser.MediaEncoding.BdInfo x => new BdInfoFileInfo(x)); } - public static IDirectoryInfo FromFileSystemPath(Model.IO.IFileSystem fs, string path) + public static IDirectoryInfo FromFileSystemPath(IFileSystem fs, string path) { return new BdInfoDirectoryInfo(fs, path); } diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index e0116c06863..58dabc628bd 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -395,7 +395,7 @@ namespace Rssdp.Infrastructure // Strange cannot convert compiler error here if I don't explicitly // assign or cast to Action first. Assignment is easier to read, // so went with that. - ProcessMessage(System.Text.UTF8Encoding.UTF8.GetString(result.Buffer, 0, result.ReceivedBytes), result.RemoteEndPoint, result.LocalIPAddress); + ProcessMessage(UTF8Encoding.UTF8.GetString(result.Buffer, 0, result.ReceivedBytes), result.RemoteEndPoint, result.LocalIPAddress); } } catch (ObjectDisposedException) From d45fcdd5af70dc84d69b4d25a1484d51efa29a0f Mon Sep 17 00:00:00 2001 From: KonH Date: Sun, 3 Oct 2021 11:02:53 +0700 Subject: [PATCH 157/278] Fix warning: Redundant control flow jump statement (#2149) --- MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs index 600a4415708..e8fd18ae490 100644 --- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs +++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs @@ -208,7 +208,6 @@ namespace MediaBrowser.Model.Dlna if (string.IsNullOrEmpty(orgPn)) { contentFeatureList.Add(orgOp.TrimStart(';') + orgCi + dlnaflags); - continue; } else { From 6cbfdea4c018a88864f1c7aa8f5b2535a9952b31 Mon Sep 17 00:00:00 2001 From: KonH Date: Sun, 3 Oct 2021 11:05:18 +0700 Subject: [PATCH 158/278] Fix warning: Type cast is redundant (#2149) --- Jellyfin.Api/Controllers/DisplayPreferencesController.cs | 2 +- Jellyfin.Api/Controllers/ItemUpdateController.cs | 4 ++-- Jellyfin.Api/Helpers/ClaimHelpers.cs | 2 +- .../Migrations/Routines/MigrateDisplayPreferencesDb.cs | 2 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 2 +- RSSDP/SsdpDeviceLocator.cs | 2 +- tests/Jellyfin.Networking.Tests/NetworkParseTests.cs | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 87b4577b62e..2079476d0a3 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -137,7 +137,7 @@ namespace Jellyfin.Api.Controllers } var existingDisplayPreferences = _displayPreferencesManager.GetDisplayPreferences(userId, itemId, client); - existingDisplayPreferences.IndexBy = Enum.TryParse(displayPreferences.IndexBy, true, out var indexBy) ? indexBy : (IndexingKind?)null; + existingDisplayPreferences.IndexBy = Enum.TryParse(displayPreferences.IndexBy, true, out var indexBy) ? indexBy : null; existingDisplayPreferences.ShowBackdrop = displayPreferences.ShowBackdrop; existingDisplayPreferences.ShowSidebar = displayPreferences.ShowSidebar; diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs index 64d7b2f3e09..fd137f98f1e 100644 --- a/Jellyfin.Api/Controllers/ItemUpdateController.cs +++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs @@ -263,8 +263,8 @@ namespace Jellyfin.Api.Controllers item.DateCreated = NormalizeDateTime(request.DateCreated.Value); } - item.EndDate = request.EndDate.HasValue ? NormalizeDateTime(request.EndDate.Value) : (DateTime?)null; - item.PremiereDate = request.PremiereDate.HasValue ? NormalizeDateTime(request.PremiereDate.Value) : (DateTime?)null; + item.EndDate = request.EndDate.HasValue ? NormalizeDateTime(request.EndDate.Value) : null; + item.PremiereDate = request.PremiereDate.HasValue ? NormalizeDateTime(request.PremiereDate.Value) : null; item.ProductionYear = request.ProductionYear; item.OfficialRating = string.IsNullOrWhiteSpace(request.OfficialRating) ? null : request.OfficialRating; item.CustomRating = request.CustomRating; diff --git a/Jellyfin.Api/Helpers/ClaimHelpers.cs b/Jellyfin.Api/Helpers/ClaimHelpers.cs index 29e6b4193ea..c1c2f93b496 100644 --- a/Jellyfin.Api/Helpers/ClaimHelpers.cs +++ b/Jellyfin.Api/Helpers/ClaimHelpers.cs @@ -20,7 +20,7 @@ namespace Jellyfin.Api.Helpers var value = GetClaimValue(user, InternalClaimTypes.UserId); return string.IsNullOrEmpty(value) ? null - : (Guid?)Guid.Parse(value); + : Guid.Parse(value); } /// diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index 6ff59626ded..40f871759cc 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -120,7 +120,7 @@ namespace Jellyfin.Server.Migrations.Routines var displayPreferences = new DisplayPreferences(dtoUserId, itemId, client) { - IndexBy = Enum.TryParse(dto.IndexBy, true, out var indexBy) ? indexBy : (IndexingKind?)null, + IndexBy = Enum.TryParse(dto.IndexBy, true, out var indexBy) ? indexBy : null, ShowBackdrop = dto.ShowBackdrop, ShowSidebar = dto.ShowSidebar, ScrollDirection = dto.ScrollDirection, diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 635420a76a6..84d99d550f6 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -1229,7 +1229,7 @@ namespace MediaBrowser.Model.Dlna bool result = IsAudioEligibleForDirectPlay(item, maxBitrate, playMethod); - return (result, result ? (TranscodeReason?)null : TranscodeReason.ContainerBitrateExceedsLimit); + return (result, result ? null : TranscodeReason.ContainerBitrateExceedsLimit); } public static SubtitleProfile GetSubtitleProfile( diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index 188e298e2aa..3a52b2a3e6a 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -513,7 +513,7 @@ namespace Rssdp.Infrastructure return TimeSpan.Zero; } - return (TimeSpan)(headerValue.MaxAge ?? headerValue.SharedMaxAge ?? TimeSpan.Zero); + return headerValue.MaxAge ?? headerValue.SharedMaxAge ?? TimeSpan.Zero; } private void RemoveExpiredDevicesFromCache() diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index 97c14d463b4..a24eee6934b 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -20,7 +20,7 @@ namespace Jellyfin.Networking.Tests CallBase = true }; configManager.Setup(x => x.GetConfiguration(It.IsAny())).Returns(conf); - return (IConfigurationManager)configManager.Object; + return configManager.Object; } /// From d284e09a8a41ea0a4359b89aecb17c15806b32a2 Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Sun, 3 Oct 2021 18:38:21 +0200 Subject: [PATCH 159/278] Change .vscode/launch.json to use .NET 6 --- .vscode/launch.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index e55ea224857..b82956a721d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build", - "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net5.0/jellyfin.dll", + "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net6.0/jellyfin.dll", "args": [], "cwd": "${workspaceFolder}/Jellyfin.Server", "console": "internalConsole", @@ -22,7 +22,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build", - "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net5.0/jellyfin.dll", + "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net6.0/jellyfin.dll", "args": ["--nowebclient"], "cwd": "${workspaceFolder}/Jellyfin.Server", "console": "internalConsole", From 9af16fcb6c892238b734c267873b1fc137d38e66 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 3 Oct 2021 19:52:38 +0200 Subject: [PATCH 160/278] Remove workaround for dotnet/runtime#42790 --- Emby.Dlna/DlnaManager.cs | 9 ++++-- Emby.Drawing/ImageProcessor.cs | 2 +- .../AppBase/ConfigurationHelper.cs | 9 +++--- .../LiveTv/EmbyTV/DirectRecorder.cs | 6 ++-- .../LiveTv/EmbyTV/EmbyTV.cs | 12 +++---- .../LiveTv/EmbyTV/EncodedRecorder.cs | 2 +- .../Controllers/RemoteImageController.cs | 31 ------------------- .../Savers/BaseXmlSaver.cs | 12 +++++-- .../Subtitles/SubtitleEncoder.cs | 1 - MediaBrowser.Model/IO/AsyncFile.cs | 19 ++++++++++++ MediaBrowser.Providers/Manager/ImageSaver.cs | 6 ++-- .../Manager/ItemImageProvider.cs | 2 +- .../Manager/ProviderManager.cs | 3 +- .../Plugins/AudioDb/AudioDbAlbumProvider.cs | 7 +++-- .../Plugins/AudioDb/AudioDbArtistProvider.cs | 6 ++-- .../Studios/StudiosImageProvider.cs | 7 ++--- .../Subtitles/SubtitleManager.cs | 6 ++-- .../Savers/BaseNfoSaver.cs | 11 +++++-- 18 files changed, 77 insertions(+), 74 deletions(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 8fe9d484e71..73e8a0008ea 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -359,14 +359,17 @@ namespace Emby.Dlna // The stream should exist as we just got its name from GetManifestResourceNames using (var stream = _assembly.GetManifestResourceStream(name)!) { + var length = stream.Length; var fileInfo = _fileSystem.GetFileInfo(path); - if (!fileInfo.Exists || fileInfo.Length != stream.Length) + if (!fileInfo.Exists || fileInfo.Length != length) { Directory.CreateDirectory(systemProfilesPath); - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) + var fileOptions = AsyncFile.WriteOptions; + fileOptions.Mode = FileMode.CreateNew; + fileOptions.PreallocationSize = length; + using (var fileStream = new FileStream(path, fileOptions)) { await stream.CopyToAsync(fileStream).ConfigureAwait(false); } diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 9b130fdfd8f..ac73cfa421b 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -102,7 +102,7 @@ namespace Emby.Drawing { var file = await ProcessImage(options).ConfigureAwait(false); - using (var fileStream = new FileStream(file.Item1, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) + using (var fileStream = AsyncFile.OpenRead(file.Item1)) { await fileStream.CopyToAsync(toStream).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index 0308a68e42d..3a916e5c013 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -41,20 +41,19 @@ namespace Emby.Server.Implementations.AppBase xmlSerializer.SerializeToStream(configuration, stream); // Take the object we just got and serialize it back to bytes - byte[] newBytes = stream.GetBuffer(); - int newBytesLen = (int)stream.Length; + Span newBytes = stream.GetBuffer().AsSpan(0, (int)stream.Length); // If the file didn't exist before, or if something has changed, re-save - if (buffer == null || !newBytes.AsSpan(0, newBytesLen).SequenceEqual(buffer)) + if (buffer == null || !newBytes.SequenceEqual(buffer)) { var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException($"Provided path ({path}) is not valid.", nameof(path)); Directory.CreateDirectory(directory); + // Save it after load in case we got new items - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None)) { - fs.Write(newBytes, 0, newBytesLen); + fs.Write(newBytes); } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 41381d55bdc..6937cc09717 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -46,8 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile))); - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) + using (var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) { onStarted(); @@ -79,8 +78,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile))); - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.CopyToBufferSize, FileOptions.Asynchronous); + await using var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.CopyToBufferSize, FileOptions.Asynchronous); onStarted(); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 64e54aa99e7..980b42729a1 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1848,14 +1848,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return; } - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.None)) + using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { var settings = new XmlWriterSettings { Indent = true, - Encoding = Encoding.UTF8, - CloseOutput = false + Encoding = Encoding.UTF8 }; using (var writer = XmlWriter.Create(stream, settings)) @@ -1913,14 +1911,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return; } - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.None)) + using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { var settings = new XmlWriterSettings { Indent = true, - Encoding = Encoding.UTF8, - CloseOutput = false + Encoding = Encoding.UTF8 }; var options = _config.GetNfoConfiguration(); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 3633fa3e106..016a3e83fa4 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV Directory.CreateDirectory(Path.GetDirectoryName(logFilePath)); // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - _logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); + _logFileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); await JsonSerializer.SerializeAsync(_logFileStream, mediaSource, _jsonOptions, cancellationToken).ConfigureAwait(false); await _logFileStream.WriteAsync(Encoding.UTF8.GetBytes(Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine), cancellationToken).ConfigureAwait(false); diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs index 8a33b12f4c7..35921ede8f1 100644 --- a/Jellyfin.Api/Controllers/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/RemoteImageController.cs @@ -183,36 +183,5 @@ namespace Jellyfin.Api.Controllers { return Path.Combine(_applicationPaths.CachePath, "remote-images", filename.Substring(0, 1), filename); } - - /// - /// Downloads the image. - /// - /// The URL. - /// The URL hash. - /// The pointer cache path. - /// Task. - private async Task DownloadImage(Uri url, Guid urlHash, string pointerCachePath) - { - var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); - using var response = await httpClient.GetAsync(url).ConfigureAwait(false); - if (response.Content.Headers.ContentType?.MediaType == null) - { - throw new ResourceNotFoundException(nameof(response.Content.Headers.ContentType)); - } - - var ext = response.Content.Headers.ContentType.MediaType.AsSpan().RightPart('/').ToString(); - var fullCachePath = GetFullCachePath(urlHash + "." + ext); - - var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid."); - Directory.CreateDirectory(fullCacheDirectory); - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); - await response.Content.CopyToAsync(fileStream).ConfigureAwait(false); - - var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath)); - Directory.CreateDirectory(pointerCacheDirectory); - await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath, CancellationToken.None) - .ConfigureAwait(false); - } } } diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index 6f66fd61b01..5e7af23fc35 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -113,11 +113,19 @@ namespace MediaBrowser.LocalMetadata.Savers { var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException($"Provided path ({path}) is not valid.", nameof(path)); Directory.CreateDirectory(directory); + // On Windows, savint the file will fail if the file is hidden or readonly FileSystem.SetAttributes(path, false, false); - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using (var filestream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None)) + var fileStreamOptions = new FileStreamOptions() + { + Mode = FileMode.Create, + Access = FileAccess.Write, + Share = FileShare.None, + PreallocationSize = stream.Length + }; + + using (var filestream = new FileStream(path, fileStreamOptions)) { stream.CopyTo(filestream); } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 022cdbe9d35..bfc17904cd6 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -679,7 +679,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles if (!string.Equals(text, newText, StringComparison.Ordinal)) { - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) using (var writer = new StreamWriter(fileStream, encoding)) { diff --git a/MediaBrowser.Model/IO/AsyncFile.cs b/MediaBrowser.Model/IO/AsyncFile.cs index caee9c52899..3c8007d1c4b 100644 --- a/MediaBrowser.Model/IO/AsyncFile.cs +++ b/MediaBrowser.Model/IO/AsyncFile.cs @@ -7,6 +7,25 @@ namespace MediaBrowser.Model.IO /// public static class AsyncFile { + /// + /// Gets the default for reading files async. + /// + public static FileStreamOptions ReadOptions => new FileStreamOptions() + { + Options = FileOptions.Asynchronous + }; + + /// + /// Gets the default for writing files async. + /// + public static FileStreamOptions WriteOptions => new FileStreamOptions() + { + Mode = FileMode.OpenOrCreate, + Access = FileAccess.Write, + Share = FileShare.None, + Options = FileOptions.Asynchronous + }; + /// /// Opens an existing file for reading. /// diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 4b05edd67ba..3d8dd148607 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -261,8 +261,10 @@ namespace MediaBrowser.Providers.Manager _fileSystem.SetAttributes(path, false, false); - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) + var fileStreamOptions = AsyncFile.WriteOptions; + fileStreamOptions.Mode = FileMode.Create; + fileStreamOptions.PreallocationSize = source.Length; + await using (var fs = new FileStream(path, fileStreamOptions)) { await source.CopyToAsync(fs, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 73bca5aa59c..3593697d7ed 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -163,7 +163,7 @@ namespace MediaBrowser.Providers.Manager { var mimeType = MimeTypes.GetMimeType(response.Path); - var stream = new FileStream(response.Path, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); + var stream = AsyncFile.OpenRead(response.Path); await _providerManager.SaveImage(item, stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index b51a254172a..9be887e9c36 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -210,8 +210,7 @@ namespace MediaBrowser.Providers.Manager throw new ArgumentNullException(nameof(source)); } - var fileStream = new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - + var fileStream = AsyncFile.OpenRead(source); return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken); } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs index 3e0b0014eb8..ebdcb94eb6c 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs @@ -172,8 +172,11 @@ namespace MediaBrowser.Providers.Plugins.AudioDb using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); + + var fileStreamOptions = AsyncFile.WriteOptions; + fileStreamOptions.Mode = FileMode.Create; + fileStreamOptions.PreallocationSize = stream.Length; + await using var xmlFileStream = new FileStream(path, fileStreamOptions); await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs index e0b2f9c5888..321013b17cd 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs @@ -154,8 +154,10 @@ namespace MediaBrowser.Providers.Plugins.AudioDb Directory.CreateDirectory(Path.GetDirectoryName(path)); - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); + var fileStreamOptions = AsyncFile.WriteOptions; + fileStreamOptions.Mode = FileMode.Create; + fileStreamOptions.PreallocationSize = stream.Length; + await using var xmlFileStream = new FileStream(path, fileStreamOptions); await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs index 7b2454efc99..a1873eaae46 100644 --- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs @@ -172,19 +172,16 @@ namespace MediaBrowser.Providers.Studios public IEnumerable GetAvailableImages(string file) { - using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read); + using var fileStream = File.OpenRead(file); using var reader = new StreamReader(fileStream); - var lines = new List(); foreach (var line in reader.ReadAllLines()) { if (!string.IsNullOrWhiteSpace(line)) { - lines.Add(line); + yield return line; } } - - return lines; } } } diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 772e617abb3..e625b0dd2a2 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -244,8 +244,10 @@ namespace MediaBrowser.Providers.Subtitles { Directory.CreateDirectory(Path.GetDirectoryName(savePath)); - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, FileStreamBufferSize, FileOptions.Asynchronous); + var fileOptions = AsyncFile.WriteOptions; + fileOptions.Mode = FileMode.CreateNew; + fileOptions.PreallocationSize = stream.Length; + using var fs = new FileStream(savePath, fileOptions); await stream.CopyToAsync(fs).ConfigureAwait(false); return; diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 38726a6f083..594402258a1 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -203,8 +203,15 @@ namespace MediaBrowser.XbmcMetadata.Savers // On Windows, saving the file will fail if the file is hidden or readonly FileSystem.SetAttributes(path, false, false); - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using (var filestream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None)) + var fileStreamOptions = new FileStreamOptions() + { + Mode = FileMode.Create, + Access = FileAccess.Write, + Share = FileShare.None, + PreallocationSize = stream.Length + }; + + using (var filestream = new FileStream(path, fileStreamOptions)) { stream.CopyTo(filestream); } From 078200d8d4ba6d986cce8881b17d30b82e58b038 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 2 Oct 2021 13:23:05 -0400 Subject: [PATCH 161/278] Fix warnings in Sorting --- .../Sorting/AiredEpisodeOrderComparer.cs | 12 +++---- .../Sorting/AlbumArtistComparer.cs | 12 +++---- .../Sorting/AlbumComparer.cs | 12 +++---- .../Sorting/CriticRatingComparer.cs | 12 +++---- .../Sorting/DateCreatedComparer.cs | 12 +++---- .../Sorting/DatePlayedComparer.cs | 12 +++---- .../Sorting/NameComparer.cs | 12 +++---- .../Sorting/PlayCountComparer.cs | 36 +++++++++---------- .../Sorting/PremiereDateComparer.cs | 12 +++---- .../Sorting/ProductionYearComparer.cs | 12 +++---- .../Sorting/RandomComparer.cs | 12 +++---- .../Sorting/RuntimeComparer.cs | 12 +++---- .../Sorting/SortNameComparer.cs | 12 +++---- .../Sorting/StudioComparer.cs | 12 +++---- 14 files changed, 96 insertions(+), 96 deletions(-) diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 037eb170aca..db8b689491a 100644 --- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -10,6 +10,12 @@ namespace Emby.Server.Implementations.Sorting { public class AiredEpisodeOrderComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.AiredEpisodeOrder; + /// /// Compares the specified x. /// @@ -155,11 +161,5 @@ namespace Emby.Server.Implementations.Sorting return comparisonResult; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.AiredEpisodeOrder; } } diff --git a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs index 42e644970ce..bd196662380 100644 --- a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs @@ -12,6 +12,12 @@ namespace Emby.Server.Implementations.Sorting /// public class AlbumArtistComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.AlbumArtist; + /// /// Compares the specified x. /// @@ -34,11 +40,5 @@ namespace Emby.Server.Implementations.Sorting return audio?.AlbumArtists.FirstOrDefault(); } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.AlbumArtist; } } diff --git a/Emby.Server.Implementations/Sorting/AlbumComparer.cs b/Emby.Server.Implementations/Sorting/AlbumComparer.cs index 1db3f5e9cae..fe7dc84cbfa 100644 --- a/Emby.Server.Implementations/Sorting/AlbumComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumComparer.cs @@ -11,6 +11,12 @@ namespace Emby.Server.Implementations.Sorting /// public class AlbumComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.Album; + /// /// Compares the specified x. /// @@ -33,11 +39,5 @@ namespace Emby.Server.Implementations.Sorting return audio == null ? string.Empty : audio.Album; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.Album; } } diff --git a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs index d20dedc2d4d..ba1835e4f2a 100644 --- a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs @@ -9,6 +9,12 @@ namespace Emby.Server.Implementations.Sorting /// public class CriticRatingComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.CriticRating; + /// /// Compares the specified x. /// @@ -24,11 +30,5 @@ namespace Emby.Server.Implementations.Sorting { return x?.CriticRating ?? 0; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.CriticRating; } } diff --git a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs index d3f10f78cb3..8b460166caa 100644 --- a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs @@ -10,6 +10,12 @@ namespace Emby.Server.Implementations.Sorting /// public class DateCreatedComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.DateCreated; + /// /// Compares the specified x. /// @@ -30,11 +36,5 @@ namespace Emby.Server.Implementations.Sorting return DateTime.Compare(x.DateCreated, y.DateCreated); } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.DateCreated; } } diff --git a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs index 08a44319f2e..ec818253be4 100644 --- a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs @@ -32,6 +32,12 @@ namespace Emby.Server.Implementations.Sorting /// The user data repository. public IUserDataManager UserDataRepository { get; set; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.DatePlayed; + /// /// Compares the specified x. /// @@ -59,11 +65,5 @@ namespace Emby.Server.Implementations.Sorting return DateTime.MinValue; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.DatePlayed; } } diff --git a/Emby.Server.Implementations/Sorting/NameComparer.cs b/Emby.Server.Implementations/Sorting/NameComparer.cs index 4de81a69e37..8f87717f495 100644 --- a/Emby.Server.Implementations/Sorting/NameComparer.cs +++ b/Emby.Server.Implementations/Sorting/NameComparer.cs @@ -10,6 +10,12 @@ namespace Emby.Server.Implementations.Sorting /// public class NameComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.Name; + /// /// Compares the specified x. /// @@ -30,11 +36,5 @@ namespace Emby.Server.Implementations.Sorting return string.Compare(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase); } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.Name; } } diff --git a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs index 04e4865cbf1..45c9044c524 100644 --- a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs +++ b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs @@ -19,6 +19,24 @@ namespace Emby.Server.Implementations.Sorting /// The user. public User User { get; set; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.PlayCount; + + /// + /// Gets or sets the user data repository. + /// + /// The user data repository. + public IUserDataManager UserDataRepository { get; set; } + + /// + /// Gets or sets the user manager. + /// + /// The user manager. + public IUserManager UserManager { get; set; } + /// /// Compares the specified x. /// @@ -41,23 +59,5 @@ namespace Emby.Server.Implementations.Sorting return userdata == null ? 0 : userdata.PlayCount; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.PlayCount; - - /// - /// Gets or sets the user data repository. - /// - /// The user data repository. - public IUserDataManager UserDataRepository { get; set; } - - /// - /// Gets or sets the user manager. - /// - /// The user manager. - public IUserManager UserManager { get; set; } } } diff --git a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs index c98f97bf1ec..b217556ef3a 100644 --- a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs @@ -10,6 +10,12 @@ namespace Emby.Server.Implementations.Sorting /// public class PremiereDateComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.PremiereDate; + /// /// Compares the specified x. /// @@ -52,11 +58,5 @@ namespace Emby.Server.Implementations.Sorting return DateTime.MinValue; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.PremiereDate; } } diff --git a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs index df9f9957d62..d2022df7a6e 100644 --- a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs +++ b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs @@ -9,6 +9,12 @@ namespace Emby.Server.Implementations.Sorting /// public class ProductionYearComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.ProductionYear; + /// /// Compares the specified x. /// @@ -44,11 +50,5 @@ namespace Emby.Server.Implementations.Sorting return 0; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.ProductionYear; } } diff --git a/Emby.Server.Implementations/Sorting/RandomComparer.cs b/Emby.Server.Implementations/Sorting/RandomComparer.cs index af3bc275082..bf0168222d0 100644 --- a/Emby.Server.Implementations/Sorting/RandomComparer.cs +++ b/Emby.Server.Implementations/Sorting/RandomComparer.cs @@ -10,6 +10,12 @@ namespace Emby.Server.Implementations.Sorting /// public class RandomComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.Random; + /// /// Compares the specified x. /// @@ -20,11 +26,5 @@ namespace Emby.Server.Implementations.Sorting { return Guid.NewGuid().CompareTo(Guid.NewGuid()); } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.Random; } } diff --git a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs index 1293153031f..e32e5552e44 100644 --- a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs +++ b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs @@ -12,6 +12,12 @@ namespace Emby.Server.Implementations.Sorting /// public class RuntimeComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.Runtime; + /// /// Compares the specified x. /// @@ -32,11 +38,5 @@ namespace Emby.Server.Implementations.Sorting return (x.RunTimeTicks ?? 0).CompareTo(y.RunTimeTicks ?? 0); } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.Runtime; } } diff --git a/Emby.Server.Implementations/Sorting/SortNameComparer.cs b/Emby.Server.Implementations/Sorting/SortNameComparer.cs index 8d30716d393..fb97a0349dd 100644 --- a/Emby.Server.Implementations/Sorting/SortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SortNameComparer.cs @@ -12,6 +12,12 @@ namespace Emby.Server.Implementations.Sorting /// public class SortNameComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.SortName; + /// /// Compares the specified x. /// @@ -32,11 +38,5 @@ namespace Emby.Server.Implementations.Sorting return string.Compare(x.SortName, y.SortName, StringComparison.CurrentCultureIgnoreCase); } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.SortName; } } diff --git a/Emby.Server.Implementations/Sorting/StudioComparer.cs b/Emby.Server.Implementations/Sorting/StudioComparer.cs index 6826aee3b13..4d89cfa8b28 100644 --- a/Emby.Server.Implementations/Sorting/StudioComparer.cs +++ b/Emby.Server.Implementations/Sorting/StudioComparer.cs @@ -13,6 +13,12 @@ namespace Emby.Server.Implementations.Sorting { public class StudioComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.Studio; + /// /// Compares the specified x. /// @@ -33,11 +39,5 @@ namespace Emby.Server.Implementations.Sorting return AlphanumericComparator.CompareValues(x.Studios.FirstOrDefault(), y.Studios.FirstOrDefault()); } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.Studio; } } From c09ef74b516c04b820bf7cdd1060ab7ceb102c13 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 2 Oct 2021 13:31:31 -0400 Subject: [PATCH 162/278] Fix warnings in IO --- .../IO/LibraryMonitor.cs | 36 ++++++++++--------- .../IO/ManagedFileSystem.cs | 18 +++++----- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index aa80bccd723..e9d069cd337 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -41,6 +41,25 @@ namespace Emby.Server.Implementations.IO private bool _disposed = false; + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The library manager. + /// The configuration manager. + /// The filesystem. + public LibraryMonitor( + ILogger logger, + ILibraryManager libraryManager, + IServerConfigurationManager configurationManager, + IFileSystem fileSystem) + { + _libraryManager = libraryManager; + _logger = logger; + _configurationManager = configurationManager; + _fileSystem = fileSystem; + } + /// /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. /// @@ -95,21 +114,6 @@ namespace Emby.Server.Implementations.IO } } - /// - /// Initializes a new instance of the class. - /// - public LibraryMonitor( - ILogger logger, - ILibraryManager libraryManager, - IServerConfigurationManager configurationManager, - IFileSystem fileSystem) - { - _libraryManager = libraryManager; - _logger = logger; - _configurationManager = configurationManager; - _fileSystem = fileSystem; - } - private bool IsLibraryMonitorEnabled(BaseItem item) { if (item is BasePluginFolder) @@ -199,7 +203,7 @@ namespace Emby.Server.Implementations.IO /// The LST. /// The path. /// true if [contains parent folder] [the specified LST]; otherwise, false. - /// path + /// is null. private static bool ContainsParentFolder(IEnumerable lst, string path) { if (string.IsNullOrEmpty(path)) diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 77da46cd6d7..eeee2884253 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.IO /// public class ManagedFileSystem : IFileSystem { - protected ILogger Logger; + private readonly ILogger _logger; private readonly List _shortcutHandlers = new List(); private readonly string _tempPath; @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.IO ILogger logger, IApplicationPaths applicationPaths) { - Logger = logger; + _logger = logger; _tempPath = applicationPaths.TempDirectory; } @@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.IO /// /// The filename. /// true if the specified filename is shortcut; otherwise, false. - /// filename + /// is null. public virtual bool IsShortcut(string filename) { if (string.IsNullOrEmpty(filename)) @@ -58,7 +58,7 @@ namespace Emby.Server.Implementations.IO /// /// The filename. /// System.String. - /// filename + /// is null. public virtual string? ResolveShortcut(string filename) { if (string.IsNullOrEmpty(filename)) @@ -233,9 +233,9 @@ namespace Emby.Server.Implementations.IO result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttributes.Directory; // if (!result.IsDirectory) - //{ + // { // result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden; - //} + // } if (info is FileInfo fileInfo) { @@ -254,7 +254,7 @@ namespace Emby.Server.Implementations.IO catch (FileNotFoundException ex) { // Dangling symlinks cannot be detected before opening the file unfortunately... - Logger.LogError(ex, "Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", fileInfo.FullName); + _logger.LogError(ex, "Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", fileInfo.FullName); result.Exists = false; } } @@ -343,7 +343,7 @@ namespace Emby.Server.Implementations.IO } catch (Exception ex) { - Logger.LogError(ex, "Error determining CreationTimeUtc for {FullName}", info.FullName); + _logger.LogError(ex, "Error determining CreationTimeUtc for {FullName}", info.FullName); return DateTime.MinValue; } } @@ -382,7 +382,7 @@ namespace Emby.Server.Implementations.IO } catch (Exception ex) { - Logger.LogError(ex, "Error determining LastAccessTimeUtc for {FullName}", info.FullName); + _logger.LogError(ex, "Error determining LastAccessTimeUtc for {FullName}", info.FullName); return DateTime.MinValue; } } From 8c8ca9adedf3e9df3c6fb83ee0967f0af5fc9fc3 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 2 Oct 2021 13:35:06 -0400 Subject: [PATCH 163/278] Fix warnings in UdpSocket --- Emby.Server.Implementations/Net/UdpSocket.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index 9b799e854d1..0c451ccb601 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -16,11 +16,7 @@ namespace Emby.Server.Implementations.Net public sealed class UdpSocket : ISocket, IDisposable { - private Socket _socket; private readonly int _localPort; - private bool _disposed = false; - - public Socket Socket => _socket; private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs() { @@ -32,6 +28,8 @@ namespace Emby.Server.Implementations.Net SocketFlags = SocketFlags.None }; + private Socket _socket; + private bool _disposed = false; private TaskCompletionSource _currentReceiveTaskCompletionSource; private TaskCompletionSource _currentSendTaskCompletionSource; @@ -64,6 +62,8 @@ namespace Emby.Server.Implementations.Net InitReceiveSocketAsyncEventArgs(); } + public Socket Socket => _socket; + public IPAddress LocalIPAddress { get; } private void InitReceiveSocketAsyncEventArgs() From 7ae055f740aa72ebe585d3c34a015a4a96e01ea2 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 2 Oct 2021 13:36:56 -0400 Subject: [PATCH 164/278] Fix warnings in UdpServer --- Emby.Server.Implementations/Udp/UdpServer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index 8179e26c5eb..bf51c396847 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -29,10 +29,10 @@ namespace Emby.Server.Implementations.Udp private readonly IServerApplicationHost _appHost; private readonly IConfiguration _config; - private Socket _udpSocket; - private IPEndPoint _endpoint; private readonly byte[] _receiveBuffer = new byte[8192]; + private Socket _udpSocket; + private IPEndPoint _endpoint; private bool _disposed = false; /// From b17a452d1677f6511fa9c066e16c9c7a00dca518 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 2 Oct 2021 13:41:42 -0400 Subject: [PATCH 165/278] Fix warnings in ServerApplicationPaths --- Emby.Server.Implementations/ServerApplicationPaths.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index 6cf9a8f71e0..369a2b0d882 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -12,6 +12,11 @@ namespace Emby.Server.Implementations /// /// Initializes a new instance of the class. /// + /// The path for Jellyfin's data. + /// The path for Jellyfin's logging directory. + /// The path for Jellyfin's configuration directory. + /// The path for Jellyfin's cache directory. + /// The path for Jellyfin's web UI. public ServerApplicationPaths( string programDataPath, string logDirectoryPath, From 7ea4c844c89e0bfa7a21c7a34e51182ec791ee8c Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 2 Oct 2021 13:45:11 -0400 Subject: [PATCH 166/278] Fix warnings in InstallationManager --- Emby.Server.Implementations/Updates/InstallationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 7b0afa4e24b..4a022c5dbce 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -10,8 +10,8 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Events; -using MediaBrowser.Common.Configuration; using Jellyfin.Extensions.Json; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; From aa3c33060d775c8d050bd3975cd692c80081ea66 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 2 Oct 2021 13:46:25 -0400 Subject: [PATCH 167/278] Fix warnings in Playlists --- .../Playlists/{ManualPlaylistsFolder.cs => PlaylistsFolder.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Emby.Server.Implementations/Playlists/{ManualPlaylistsFolder.cs => PlaylistsFolder.cs} (100%) diff --git a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs similarity index 100% rename from Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs rename to Emby.Server.Implementations/Playlists/PlaylistsFolder.cs From 876a902356fb5c0e502b440d591287a53e2488fc Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 2 Oct 2021 13:59:58 -0400 Subject: [PATCH 168/278] Fix warnings in Library --- .../Library/LibraryManager.cs | 4 ++-- .../Library/MediaSourceManager.cs | 2 +- .../Library/PathExtensions.cs | 2 +- .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 3 +++ .../Library/Resolvers/BaseVideoResolver.cs | 4 ++++ .../Library/Resolvers/Books/BookResolver.cs | 7 +++---- .../Library/Resolvers/Movies/MovieResolver.cs | 4 ++-- .../Library/Resolvers/SpecialFolderResolver.cs | 1 - .../Library/SearchEngine.cs | 2 +- .../Library/UserDataManager.cs | 15 +++++++-------- .../Library/UserViewManager.cs | 3 ++- .../Library/Validators/ArtistsValidator.cs | 11 +++++++---- 12 files changed, 33 insertions(+), 25 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 132486b4a67..1326f60fe55 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -647,7 +647,7 @@ namespace Emby.Server.Implementations.Library /// Determines whether a path should be ignored based on its contents - called after the contents have been read. /// /// The args. - /// true if XXXX, false otherwise + /// true if XXXX, false otherwise. private static bool ShouldResolvePathContents(ItemResolveArgs args) { // Ignore any folders containing a file called .ignore @@ -1266,7 +1266,7 @@ namespace Emby.Server.Implementations.Library /// /// The id. /// BaseItem. - /// id + /// is null. public BaseItem GetItemById(Guid id) { if (id == Guid.Empty) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 16231c73fcb..351fced3485 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -13,9 +13,9 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using Jellyfin.Extensions.Json; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 86b8039fabd..d5b855cdf26 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.Library /// The original path. /// The original sub path. /// The new sub path. - /// The result of the sub path replacement + /// The result of the sub path replacement. /// The path after replacing the sub path. /// , or is empty. public static bool TryReplaceSubPath( diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 8e1eccb10a6..60720dd2f78 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -82,6 +82,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio /// /// Determine if the supplied file data points to a music album. /// + /// The path to check. + /// The directory service. + /// true if the provided path points to a music album, false otherwise. public bool IsMusicAlbum(string path, IDirectoryService directoryService) { return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, _libraryManager); diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index b102b86cfb1..9ff99fa431f 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -275,6 +275,10 @@ namespace Emby.Server.Implementations.Library.Resolvers /// /// Determines whether [is DVD directory] [the specified directory name]. /// + /// The full path of the directory. + /// The name of the directory. + /// The directory service. + /// true if the provided directory is a DVD directory, false otherwise. protected bool IsDvdDirectory(string fullPath, string directoryName, IDirectoryService directoryService) { if (!string.Equals(directoryName, "video_ts", StringComparison.OrdinalIgnoreCase)) diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs index 68076730b3d..e685c87f1a2 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -49,13 +49,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books { var bookFiles = args.FileSystemChildren.Where(f => { - var fileExtension = Path.GetExtension(f.FullName) ?? - string.Empty; + var fileExtension = Path.GetExtension(f.FullName) + ?? string.Empty; return _validExtensions.Contains( fileExtension, - StringComparer - .OrdinalIgnoreCase); + StringComparer.OrdinalIgnoreCase); }).ToList(); // Don't return a Book if there is more (or less) than one document in the directory diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 8b55a77449b..f3b6ef0a280 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -24,6 +24,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies /// public class MovieResolver : BaseVideoResolver From fc96305e780904dd49e315a5a8b17784ee9b3492 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Oct 2021 12:01:09 +0000 Subject: [PATCH 176/278] Bump DotNet.Glob from 3.1.2 to 3.1.3 Bumps [DotNet.Glob](https://github.com/dazinator/DotNet.Glob) from 3.1.2 to 3.1.3. - [Release notes](https://github.com/dazinator/DotNet.Glob/releases) - [Changelog](https://github.com/dazinator/DotNet.Glob/blob/develop/ReleaseNotes.md) - [Commits](https://github.com/dazinator/DotNet.Glob/compare/3.1.2...3.1.3) --- updated-dependencies: - dependency-name: DotNet.Glob dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 0e1386ef550..062ad4885eb 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -34,7 +34,7 @@ - + From 017380f1ddccb46ce270f1d0df8e07d639ba3704 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Mon, 4 Oct 2021 07:43:40 -0600 Subject: [PATCH 177/278] Reference dotnet6-rc1 packages --- Emby.Dlna/Emby.Dlna.csproj | 2 +- .../Emby.Server.Implementations.csproj | 10 +++++----- Jellyfin.Api/Jellyfin.Api.csproj | 4 ++-- Jellyfin.Data/Jellyfin.Data.csproj | 2 +- .../Jellyfin.Server.Implementations.csproj | 8 ++++---- .../Security/AuthorizationContext.cs | 2 +- Jellyfin.Server.Implementations/Users/UserManager.cs | 5 +++++ Jellyfin.Server/Jellyfin.Server.csproj | 8 ++++---- MediaBrowser.Common/MediaBrowser.Common.csproj | 4 ++-- MediaBrowser.Controller/MediaBrowser.Controller.csproj | 6 +++--- .../MediaBrowser.MediaEncoding.csproj | 4 ++-- MediaBrowser.Model/MediaBrowser.Model.csproj | 4 ++-- MediaBrowser.Providers/MediaBrowser.Providers.csproj | 6 +++--- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 4 ++-- .../Jellyfin.Server.Integration.Tests.csproj | 6 +++--- .../Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 6 +++--- 16 files changed, 43 insertions(+), 38 deletions(-) diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index 1d4e3b047d3..5348aed6373 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -73,7 +73,7 @@ - + diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 428cad07155..fb197261042 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -25,11 +25,11 @@ - - - - - + + + + + diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 1c451ef6c19..f46c0cbd117 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index 19aef704c0c..f1bfaa63e0e 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -35,7 +35,7 @@ - + diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 337f5cb82ae..d9e6d794b3c 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -19,13 +19,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs index ba2cfc724ea..3ab043c648a 100644 --- a/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs +++ b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs @@ -2,12 +2,12 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Net; using System.Threading.Tasks; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; using Microsoft.Net.Http.Headers; namespace Jellyfin.Server.Implementations.Security diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 02377bfd726..704a6a84ef2 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -701,6 +701,11 @@ namespace Jellyfin.Server.Implementations.Users /// public async Task ClearProfileImageAsync(User user) { + if (user.ProfileImage == null) + { + return; + } + await using var dbContext = _dbProvider.CreateContext(); dbContext.Remove(user.ProfileImage); await dbContext.SaveChangesAsync().ConfigureAwait(false); diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 074d43fba83..6603105fa33 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -31,10 +31,10 @@ - - - - + + + + diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 6358c000012..c87d58a147e 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -19,8 +19,8 @@ - - + + diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 47cec7d77f3..997772c04c4 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -15,10 +15,10 @@ - - + + - + diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 30cfb904e1d..22bba23663e 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -24,8 +24,8 @@ - - + + diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index b0a12a9c900..0ac58e3551f 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -35,9 +35,9 @@ - + - + diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 29d6b01f237..71a3554fd69 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -16,9 +16,9 @@ - - - + + + diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index b52ea078af0..8b581857fea 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 7939c7118ef..38687ae61af 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -9,14 +9,14 @@ - - + + - + diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index b30e690a5ef..db24df240c3 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -10,13 +10,13 @@ - - + + - + From d245e45254057c62d058cfad3e56484a77093718 Mon Sep 17 00:00:00 2001 From: joanMelchor Date: Sun, 3 Oct 2021 17:12:37 +0000 Subject: [PATCH 178/278] Translated using Weblate (Catalan) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ca/ --- Emby.Server.Implementations/Localization/Core/ca.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json index 7715daa7ce1..db3c13d800b 100644 --- a/Emby.Server.Implementations/Localization/Core/ca.json +++ b/Emby.Server.Implementations/Localization/Core/ca.json @@ -15,7 +15,7 @@ "Favorites": "Preferits", "Folders": "Carpetes", "Genres": "Gèneres", - "HeaderAlbumArtists": "Artistes del Àlbum", + "HeaderAlbumArtists": "Àlbum de l'artista", "HeaderContinueWatching": "Continua Veient", "HeaderFavoriteAlbums": "Àlbums Preferits", "HeaderFavoriteArtists": "Artistes Predilectes", From 229917a2f819b9b5d8e59e116503a0dcca35c0a3 Mon Sep 17 00:00:00 2001 From: cvium Date: Mon, 4 Oct 2021 21:12:09 +0200 Subject: [PATCH 179/278] Queue refresh after subtitle upload + minor fixes --- .../Controllers/SubtitleController.cs | 2 ++ .../Subtitles/SubtitleManager.cs | 24 ++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 1849dd04786..8fb85c732b1 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -417,6 +417,8 @@ namespace Jellyfin.Api.Controllers IsForced = body.IsForced, Stream = memoryStream }).ConfigureAwait(false); + _providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); + return NoContent(); } diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 772e617abb3..8403e16abf8 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -187,8 +187,8 @@ namespace MediaBrowser.Providers.Subtitles { var saveInMediaFolder = libraryOptions.SaveSubtitlesWithMedia; - using var stream = response.Stream; - using var memoryStream = new MemoryStream(); + await using var stream = response.Stream; + await using var memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream).ConfigureAwait(false); memoryStream.Position = 0; @@ -236,7 +236,7 @@ namespace MediaBrowser.Providers.Subtitles foreach (var savePath in savePaths) { - _logger.LogInformation("Saving subtitles to {0}", savePath); + _logger.LogInformation("Saving subtitles to {SavePath}", savePath); _monitor.ReportFileSystemChangeBeginning(savePath); @@ -254,13 +254,19 @@ namespace MediaBrowser.Providers.Subtitles { // Bug in analyzer -- https://github.com/dotnet/roslyn-analyzers/issues/5160 #pragma warning disable CA1508 - exs ??= new List() - { - ex - }; + if (exs == null) #pragma warning restore CA1508 - - } + { + exs = new List + { + ex + }; + } + else + { + exs.Add(ex); + } + } finally { _monitor.ReportFileSystemChangeComplete(savePath, false); From 74d75fad4693ba7e6f07e752ecd5b57747f5f165 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 5 Oct 2021 16:59:11 +0200 Subject: [PATCH 180/278] Improve test coverage for QuickConnectManager --- .../QuickConnect/QuickConnectManagerTests.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs index 043363ae3de..28d832ef87d 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using AutoFixture; using AutoFixture.AutoMoq; using Emby.Server.Implementations.QuickConnect; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; @@ -51,6 +52,21 @@ namespace Jellyfin.Server.Implementations.Tests.QuickConnect public void IsEnabled_QuickConnectUnavailable_False() => Assert.False(_quickConnectManager.IsEnabled); + [Theory] + [InlineData("", "DeviceId", "Client", "1.0.0")] + [InlineData("Device", "", "Client", "1.0.0")] + [InlineData("Device", "DeviceId", "", "1.0.0")] + [InlineData("Device", "DeviceId", "Client", "")] + public void TryConnect_InvalidAuthorizationInfo_ThrowsArgumentException(string device, string deviceId, string client, string version) + => Assert.Throws(() => _quickConnectManager.TryConnect( + new AuthorizationInfo + { + Device = device, + DeviceId = deviceId, + Client = client, + Version = version + })); + [Fact] public void TryConnect_QuickConnectUnavailable_ThrowsAuthenticationException() => Assert.Throws(() => _quickConnectManager.TryConnect(_quickConnectAuthInfo)); @@ -63,6 +79,10 @@ namespace Jellyfin.Server.Implementations.Tests.QuickConnect public void AuthorizeRequest_QuickConnectUnavailable_ThrowsAuthenticationException() => Assert.ThrowsAsync(() => _quickConnectManager.AuthorizeRequest(Guid.Empty, string.Empty)); + [Fact] + public void GetAuthorizedRequest_QuickConnectUnavailable_ThrowsAuthenticationException() + => Assert.Throws(() => _quickConnectManager.GetAuthorizedRequest(string.Empty)); + [Fact] public void IsEnabled_QuickConnectAvailable_True() { @@ -79,6 +99,20 @@ namespace Jellyfin.Server.Implementations.Tests.QuickConnect Assert.Equal(res1, res2); } + [Fact] + public void CheckRequestStatus_UnknownSecret_ThrowsResourceNotFoundException() + { + _config.QuickConnectAvailable = true; + Assert.Throws(() => _quickConnectManager.CheckRequestStatus("Unknown secret")); + } + + [Fact] + public void GetAuthorizedRequest_UnknownSecret_ThrowsResourceNotFoundException() + { + _config.QuickConnectAvailable = true; + Assert.Throws(() => _quickConnectManager.GetAuthorizedRequest("Unknown secret")); + } + [Fact] public async Task AuthorizeRequest_QuickConnectAvailable_Success() { From 7abdf71c49b271281b5500c3cbbebc7ead06f0e2 Mon Sep 17 00:00:00 2001 From: cvium Date: Tue, 5 Oct 2021 19:49:43 +0200 Subject: [PATCH 181/278] Revert to old line --- MediaBrowser.Providers/Subtitles/SubtitleManager.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 8403e16abf8..78ca76e4b72 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -254,18 +254,8 @@ namespace MediaBrowser.Providers.Subtitles { // Bug in analyzer -- https://github.com/dotnet/roslyn-analyzers/issues/5160 #pragma warning disable CA1508 - if (exs == null) + (exs ??= new List()).Add(ex); #pragma warning restore CA1508 - { - exs = new List - { - ex - }; - } - else - { - exs.Add(ex); - } } finally { From 67147400bfbecc69d38db5faaea23c9f8d92807b Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 5 Oct 2021 21:47:59 +0200 Subject: [PATCH 182/278] Fix issue #6123 --- Emby.Naming/Common/NamingOptions.cs | 6 ++ Emby.Naming/Video/ExtraResolver.cs | 6 +- .../Jellyfin.Naming.Tests/Video/ExtraTests.cs | 60 +++++++++---------- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 915ce42cc9d..248d1800d6e 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -478,6 +478,12 @@ namespace Emby.Naming.Common "-deleted", MediaType.Video), + new ExtraRule( + ExtraType.DeletedScene, + ExtraRuleType.Suffix, + "-deletedscene", + MediaType.Video), + new ExtraRule( ExtraType.Clip, ExtraRuleType.Suffix, diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs index a32af002cc0..7bc226614e9 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -11,6 +11,7 @@ namespace Emby.Naming.Video /// public class ExtraResolver { + private static readonly char[] _digits = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; private readonly NamingOptions _options; /// @@ -62,9 +63,10 @@ namespace Emby.Naming.Video } else if (rule.RuleType == ExtraRuleType.Suffix) { - var filename = Path.GetFileNameWithoutExtension(pathSpan); + // Trim the digits from the end of the filename so we can recognize things like -trailer2 + var filename = Path.GetFileNameWithoutExtension(pathSpan).TrimEnd(_digits); - if (filename.Contains(rule.Token, StringComparison.OrdinalIgnoreCase)) + if (filename.EndsWith(rule.Token, StringComparison.OrdinalIgnoreCase)) { result.ExtraType = rule.ExtraType; result.Rule = rule; diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs index f872f94f8e2..d13e89cee84 100644 --- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs @@ -18,30 +18,31 @@ namespace Jellyfin.Naming.Tests.Video [Fact] public void TestKodiExtras() { - Test("trailer.mp4", ExtraType.Trailer, _videoOptions); - Test("300-trailer.mp4", ExtraType.Trailer, _videoOptions); + Test("trailer.mp4", ExtraType.Trailer); + Test("300-trailer.mp4", ExtraType.Trailer); - Test("theme.mp3", ExtraType.ThemeSong, _videoOptions); + Test("theme.mp3", ExtraType.ThemeSong); } [Fact] public void TestExpandedExtras() { - Test("trailer.mp4", ExtraType.Trailer, _videoOptions); - Test("trailer.mp3", null, _videoOptions); - Test("300-trailer.mp4", ExtraType.Trailer, _videoOptions); + Test("trailer.mp4", ExtraType.Trailer); + Test("trailer.mp3", null); + Test("300-trailer.mp4", ExtraType.Trailer); + Test("stuff trailerthings.mkv", null); - Test("theme.mp3", ExtraType.ThemeSong, _videoOptions); - Test("theme.mkv", null, _videoOptions); + Test("theme.mp3", ExtraType.ThemeSong); + Test("theme.mkv", null); - Test("300-scene.mp4", ExtraType.Scene, _videoOptions); - Test("300-scene2.mp4", ExtraType.Scene, _videoOptions); - Test("300-clip.mp4", ExtraType.Clip, _videoOptions); + Test("300-scene.mp4", ExtraType.Scene); + Test("300-scene2.mp4", ExtraType.Scene); + Test("300-clip.mp4", ExtraType.Clip); - Test("300-deleted.mp4", ExtraType.DeletedScene, _videoOptions); - Test("300-deletedscene.mp4", ExtraType.DeletedScene, _videoOptions); - Test("300-interview.mp4", ExtraType.Interview, _videoOptions); - Test("300-behindthescenes.mp4", ExtraType.BehindTheScenes, _videoOptions); + Test("300-deleted.mp4", ExtraType.DeletedScene); + Test("300-deletedscene.mp4", ExtraType.DeletedScene); + Test("300-interview.mp4", ExtraType.Interview); + Test("300-behindthescenes.mp4", ExtraType.BehindTheScenes); } [Theory] @@ -55,9 +56,9 @@ namespace Jellyfin.Naming.Tests.Video [InlineData(ExtraType.Unknown, "extras")] public void TestDirectories(ExtraType type, string dirName) { - Test(dirName + "/300.mp4", type, _videoOptions); - Test("300/" + dirName + "/something.mkv", type, _videoOptions); - Test("/data/something/Movies/300/" + dirName + "/whoknows.mp4", type, _videoOptions); + Test(dirName + "/300.mp4", type); + Test("300/" + dirName + "/something.mkv", type); + Test("/data/something/Movies/300/" + dirName + "/whoknows.mp4", type); } [Theory] @@ -66,32 +67,25 @@ namespace Jellyfin.Naming.Tests.Video [InlineData("The Big Short")] public void TestNonExtraDirectories(string dirName) { - Test(dirName + "/300.mp4", null, _videoOptions); - Test("300/" + dirName + "/something.mkv", null, _videoOptions); - Test("/data/something/Movies/300/" + dirName + "/whoknows.mp4", null, _videoOptions); - Test("/data/something/Movies/" + dirName + "/" + dirName + ".mp4", null, _videoOptions); + Test(dirName + "/300.mp4", null); + Test("300/" + dirName + "/something.mkv", null); + Test("/data/something/Movies/300/" + dirName + "/whoknows.mp4", null); + Test("/data/something/Movies/" + dirName + "/" + dirName + ".mp4", null); } [Fact] public void TestSample() { - Test("300-sample.mp4", ExtraType.Sample, _videoOptions); + Test("300-sample.mp4", ExtraType.Sample); } - private void Test(string input, ExtraType? expectedType, NamingOptions videoOptions) + private void Test(string input, ExtraType? expectedType) { - var parser = GetExtraTypeParser(videoOptions); + var parser = GetExtraTypeParser(_videoOptions); var extraType = parser.GetExtraInfo(input).ExtraType; - if (expectedType == null) - { - Assert.Null(extraType); - } - else - { - Assert.Equal(expectedType, extraType); - } + Assert.Equal(expectedType, extraType); } [Fact] From e0db541381a669ff5ba618e618e162ba0d0ef1c9 Mon Sep 17 00:00:00 2001 From: Patrick Barron <18354464+barronpm@users.noreply.github.com> Date: Tue, 5 Oct 2021 22:07:16 -0400 Subject: [PATCH 183/278] Update indentation --- .../Library/UserViewManager.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index b4ac49a38b8..eda494815db 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -341,14 +341,15 @@ namespace Emby.Server.Implementations.Library mediaTypes = mediaTypes.Distinct().ToList(); } - var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0 ? new[] - { - nameof(Person), - nameof(Studio), - nameof(Year), - nameof(MusicGenre), - nameof(Genre) - } + var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0 + ? new[] + { + nameof(Person), + nameof(Studio), + nameof(Year), + nameof(MusicGenre), + nameof(Genre) + } : Array.Empty(); var query = new InternalItemsQuery(user) From ef99225c40692b000ca67a1ec9f31cc04fac4743 Mon Sep 17 00:00:00 2001 From: Dominik Krivohlavek Date: Wed, 6 Oct 2021 10:32:28 +0200 Subject: [PATCH 184/278] Add 22/7 to split whitelist --- MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index df9753b3808..fee3bf12ea3 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -49,7 +49,8 @@ namespace MediaBrowser.MediaEncoding.Probing "LOONA 1/3", "LOONA / yyxy", "LOONA / ODD EYE CIRCLE", - "K/DA" + "K/DA", + "22/7" }; public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType? videoType, bool isAudio, string path, MediaProtocol protocol) From 03f933aaa07113b0ae6971921249691c8455d5ba Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 6 Oct 2021 11:30:45 +0200 Subject: [PATCH 185/278] Fix the last few warnings Enables TreatWarningsAsErrors for all projects --- .../ApplicationHost.cs | 38 +++---- .../Data/BaseSqliteRepository.cs | 2 +- .../Emby.Server.Implementations.csproj | 5 - .../Images/BaseDynamicImageProvider.cs | 4 +- .../Images/BaseFolderImageProvider.cs | 67 ++++++++++++ .../Images/FolderImageProvider.cs | 69 ------------ .../Images/GenreImageProvider.cs | 41 ------- .../Images/MusicAlbumImageProvider.cs | 19 ++++ .../Images/MusicGenreImageProvider.cs | 59 +++++++++++ .../Images/PhotoAlbumImageProvider.cs | 19 ++++ .../Library/Resolvers/FolderResolver.cs | 22 +--- .../Resolvers/GenericFolderResolver.cs | 27 +++++ .../Resolvers/Movies/BoxSetResolver.cs | 2 +- .../Library/Resolvers/PhotoAlbumResolver.cs | 2 +- .../Library/Resolvers/PlaylistResolver.cs | 2 +- .../Resolvers/SpecialFolderResolver.cs | 2 +- .../Library/Resolvers/TV/SeasonResolver.cs | 2 +- .../Library/Resolvers/TV/SeriesResolver.cs | 2 +- .../SchedulesDirectDtos/ImageDataDto.cs | 2 +- .../SchedulesDirectDtos/StationDto.cs | 100 +++++++++--------- .../HdHomerun/HdHomerunChannelCommands.cs | 35 ++++++ .../TunerHosts/HdHomerun/HdHomerunHost.cs | 10 +- .../TunerHosts/HdHomerun/HdHomerunManager.cs | 66 ------------ .../HdHomerun/IHdHomerunChannelCommands.cs | 11 ++ .../LegacyHdHomerunChannelCommands.cs | 38 +++++++ .../MediaBrowser.Controller.csproj | 5 - MediaBrowser.Model/MediaBrowser.Model.csproj | 5 - 27 files changed, 353 insertions(+), 303 deletions(-) create mode 100644 Emby.Server.Implementations/Images/BaseFolderImageProvider.cs create mode 100644 Emby.Server.Implementations/Images/MusicAlbumImageProvider.cs create mode 100644 Emby.Server.Implementations/Images/MusicGenreImageProvider.cs create mode 100644 Emby.Server.Implementations/Images/PhotoAlbumImageProvider.cs create mode 100644 Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs create mode 100644 Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs create mode 100644 Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs create mode 100644 Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 3a504d2f437..1f11bdad749 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -306,7 +306,7 @@ namespace Emby.Server.Implementations /// public string Name => ApplicationProductName; - private CertificateInfo CertificateInfo { get; set; } + private string CertificatePath { get; set; } public X509Certificate2 Certificate { get; private set; } @@ -548,12 +548,8 @@ namespace Emby.Server.Implementations HttpsPort = NetworkConfiguration.DefaultHttpsPort; } - CertificateInfo = new CertificateInfo - { - Path = networkConfiguration.CertificatePath, - Password = networkConfiguration.CertificatePassword - }; - Certificate = GetCertificate(CertificateInfo); + CertificatePath = networkConfiguration.CertificatePath; + Certificate = GetCertificate(CertificatePath, networkConfiguration.CertificatePassword); RegisterServices(); @@ -729,30 +725,27 @@ namespace Emby.Server.Implementations logger.LogInformation("Application directory: {ApplicationPath}", appPaths.ProgramSystemPath); } - private X509Certificate2 GetCertificate(CertificateInfo info) + private X509Certificate2 GetCertificate(string path, string password) { - var certificateLocation = info?.Path; - - if (string.IsNullOrWhiteSpace(certificateLocation)) + if (string.IsNullOrWhiteSpace(path)) { return null; } try { - if (!File.Exists(certificateLocation)) + if (!File.Exists(path)) { return null; } // Don't use an empty string password - var password = string.IsNullOrWhiteSpace(info.Password) ? null : info.Password; + password = string.IsNullOrWhiteSpace(password) ? null : password; - var localCert = new X509Certificate2(certificateLocation, password, X509KeyStorageFlags.UserKeySet); - // localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA; + var localCert = new X509Certificate2(path, password, X509KeyStorageFlags.UserKeySet); if (!localCert.HasPrivateKey) { - Logger.LogError("No private key included in SSL cert {CertificateLocation}.", certificateLocation); + Logger.LogError("No private key included in SSL cert {CertificateLocation}.", path); return null; } @@ -760,7 +753,7 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - Logger.LogError(ex, "Error loading cert from {CertificateLocation}", certificateLocation); + Logger.LogError(ex, "Error loading cert from {CertificateLocation}", path); return null; } } @@ -882,7 +875,7 @@ namespace Emby.Server.Implementations "http://" + i + ":" + HttpPort + "/" }; - if (CertificateInfo != null) + if (Certificate != null) { prefixes.Add("https://" + i + ":" + HttpsPort + "/"); } @@ -946,7 +939,7 @@ namespace Emby.Server.Implementations var newPath = networkConfig.CertificatePath; if (!string.IsNullOrWhiteSpace(newPath) - && !string.Equals(CertificateInfo?.Path, newPath, StringComparison.Ordinal)) + && !string.Equals(CertificatePath, newPath, StringComparison.Ordinal)) { if (File.Exists(newPath)) { @@ -1293,11 +1286,4 @@ namespace Emby.Server.Implementations _disposed = true; } } - - internal class CertificateInfo - { - public string Path { get; set; } - - public string Password { get; set; } - } } diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 4f6c811028f..73c31f49d1f 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -98,7 +98,7 @@ namespace Emby.Server.Implementations.Data /// The write connection. protected SQLiteDatabaseConnection WriteConnection { get; set; } - protected ManagedConnection GetConnection(bool _ = false) + protected ManagedConnection GetConnection(bool readOnly = false) { WriteLock.Wait(); if (WriteConnection != null) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 428cad07155..82e4c3d6972 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -47,11 +47,6 @@ true AD0001 - false - - - - true diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 4a026fd218a..75898694579 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -65,13 +65,13 @@ namespace Emby.Server.Implementations.Images if (SupportedImages.Contains(ImageType.Primary)) { var primaryResult = await FetchAsync(item, ImageType.Primary, options, cancellationToken).ConfigureAwait(false); - updateType = updateType | primaryResult; + updateType |= primaryResult; } if (SupportedImages.Contains(ImageType.Thumb)) { var thumbResult = await FetchAsync(item, ImageType.Thumb, options, cancellationToken).ConfigureAwait(false); - updateType = updateType | thumbResult; + updateType |= thumbResult; } return updateType; diff --git a/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs b/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs new file mode 100644 index 00000000000..1c69056d208 --- /dev/null +++ b/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs @@ -0,0 +1,67 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System.Collections.Generic; +using Jellyfin.Data.Enums; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Images +{ + public abstract class BaseFolderImageProvider : BaseDynamicImageProvider + where T : Folder, new() + { + private readonly ILibraryManager _libraryManager; + + public BaseFolderImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) + : base(fileSystem, providerManager, applicationPaths, imageProcessor) + { + _libraryManager = libraryManager; + } + + protected override IReadOnlyList GetItemsWithImages(BaseItem item) + { + return _libraryManager.GetItemList(new InternalItemsQuery + { + Parent = item, + DtoOptions = new DtoOptions(true), + ImageTypes = new ImageType[] { ImageType.Primary }, + OrderBy = new (string, SortOrder)[] + { + (ItemSortBy.IsFolder, SortOrder.Ascending), + (ItemSortBy.SortName, SortOrder.Ascending) + }, + Limit = 1 + }); + } + + protected override string CreateImage(BaseItem item, IReadOnlyCollection itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + { + return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary); + } + + protected override bool Supports(BaseItem item) + { + return item is T; + } + + protected override bool HasChangedByDate(BaseItem item, ItemImageInfo image) + { + if (item is MusicAlbum) + { + return false; + } + + return base.HasChangedByDate(item, image); + } + } +} diff --git a/Emby.Server.Implementations/Images/FolderImageProvider.cs b/Emby.Server.Implementations/Images/FolderImageProvider.cs index 859017f8691..4376bd356c2 100644 --- a/Emby.Server.Implementations/Images/FolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/FolderImageProvider.cs @@ -2,69 +2,16 @@ #pragma warning disable CS1591 -using System.Collections.Generic; -using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Images { - public abstract class BaseFolderImageProvider : BaseDynamicImageProvider - where T : Folder, new() - { - protected ILibraryManager _libraryManager; - - public BaseFolderImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) - : base(fileSystem, providerManager, applicationPaths, imageProcessor) - { - _libraryManager = libraryManager; - } - - protected override IReadOnlyList GetItemsWithImages(BaseItem item) - { - return _libraryManager.GetItemList(new InternalItemsQuery - { - Parent = item, - DtoOptions = new DtoOptions(true), - ImageTypes = new ImageType[] { ImageType.Primary }, - OrderBy = new System.ValueTuple[] - { - new System.ValueTuple(ItemSortBy.IsFolder, SortOrder.Ascending), - new System.ValueTuple(ItemSortBy.SortName, SortOrder.Ascending) - }, - Limit = 1 - }); - } - - protected override string CreateImage(BaseItem item, IReadOnlyCollection itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) - { - return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary); - } - - protected override bool Supports(BaseItem item) - { - return item is T; - } - - protected override bool HasChangedByDate(BaseItem item, ItemImageInfo image) - { - if (item is MusicAlbum) - { - return false; - } - - return base.HasChangedByDate(item, image); - } - } - public class FolderImageProvider : BaseFolderImageProvider { public FolderImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) @@ -87,20 +34,4 @@ namespace Emby.Server.Implementations.Images return true; } } - - public class MusicAlbumImageProvider : BaseFolderImageProvider - { - public MusicAlbumImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) - : base(fileSystem, providerManager, applicationPaths, imageProcessor, libraryManager) - { - } - } - - public class PhotoAlbumImageProvider : BaseFolderImageProvider - { - public PhotoAlbumImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) - : base(fileSystem, providerManager, applicationPaths, imageProcessor, libraryManager) - { - } - } } diff --git a/Emby.Server.Implementations/Images/GenreImageProvider.cs b/Emby.Server.Implementations/Images/GenreImageProvider.cs index 6da431c68e0..1f5090f7f58 100644 --- a/Emby.Server.Implementations/Images/GenreImageProvider.cs +++ b/Emby.Server.Implementations/Images/GenreImageProvider.cs @@ -8,7 +8,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; @@ -19,46 +18,6 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Images { - /// - /// Class MusicGenreImageProvider. - /// - public class MusicGenreImageProvider : BaseDynamicImageProvider - { - /// - /// The library manager. - /// - private readonly ILibraryManager _libraryManager; - - public MusicGenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) - { - _libraryManager = libraryManager; - } - - /// - /// Get children objects used to create an music genre image. - /// - /// The music genre used to create the image. - /// Any relevant children objects. - protected override IReadOnlyList GetItemsWithImages(BaseItem item) - { - return _libraryManager.GetItemList(new InternalItemsQuery - { - Genres = new[] { item.Name }, - IncludeItemTypes = new[] - { - nameof(MusicAlbum), - nameof(MusicVideo), - nameof(Audio) - }, - OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, - Limit = 4, - Recursive = true, - ImageTypes = new[] { ImageType.Primary }, - DtoOptions = new DtoOptions(false) - }); - } - } - /// /// Class GenreImageProvider. /// diff --git a/Emby.Server.Implementations/Images/MusicAlbumImageProvider.cs b/Emby.Server.Implementations/Images/MusicAlbumImageProvider.cs new file mode 100644 index 00000000000..ce836736382 --- /dev/null +++ b/Emby.Server.Implementations/Images/MusicAlbumImageProvider.cs @@ -0,0 +1,19 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.Images +{ + public class MusicAlbumImageProvider : BaseFolderImageProvider + { + public MusicAlbumImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) + : base(fileSystem, providerManager, applicationPaths, imageProcessor, libraryManager) + { + } + } +} diff --git a/Emby.Server.Implementations/Images/MusicGenreImageProvider.cs b/Emby.Server.Implementations/Images/MusicGenreImageProvider.cs new file mode 100644 index 00000000000..baf1c905170 --- /dev/null +++ b/Emby.Server.Implementations/Images/MusicGenreImageProvider.cs @@ -0,0 +1,59 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System.Collections.Generic; +using Jellyfin.Data.Enums; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Images +{ + /// + /// Class MusicGenreImageProvider. + /// + public class MusicGenreImageProvider : BaseDynamicImageProvider + { + /// + /// The library manager. + /// + private readonly ILibraryManager _libraryManager; + + public MusicGenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) + { + _libraryManager = libraryManager; + } + + /// + /// Get children objects used to create an music genre image. + /// + /// The music genre used to create the image. + /// Any relevant children objects. + protected override IReadOnlyList GetItemsWithImages(BaseItem item) + { + return _libraryManager.GetItemList(new InternalItemsQuery + { + Genres = new[] { item.Name }, + IncludeItemTypes = new[] + { + nameof(MusicAlbum), + nameof(MusicVideo), + nameof(Audio) + }, + OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, + Limit = 4, + Recursive = true, + ImageTypes = new[] { ImageType.Primary }, + DtoOptions = new DtoOptions(false) + }); + } + } +} diff --git a/Emby.Server.Implementations/Images/PhotoAlbumImageProvider.cs b/Emby.Server.Implementations/Images/PhotoAlbumImageProvider.cs new file mode 100644 index 00000000000..1ddb4c75798 --- /dev/null +++ b/Emby.Server.Implementations/Images/PhotoAlbumImageProvider.cs @@ -0,0 +1,19 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.Images +{ + public class PhotoAlbumImageProvider : BaseFolderImageProvider + { + public PhotoAlbumImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) + : base(fileSystem, providerManager, applicationPaths, imageProcessor, libraryManager) + { + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs index 7aaee017dd3..db7703cd60a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs @@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Library.Resolvers /// /// Class FolderResolver. /// - public class FolderResolver : FolderResolver + public class FolderResolver : GenericFolderResolver { /// /// Gets the priority. @@ -32,24 +32,4 @@ namespace Emby.Server.Implementations.Library.Resolvers return null; } } - - /// - /// Class FolderResolver. - /// - /// The type of the T item type. - public abstract class FolderResolver : ItemResolver - where TItemType : Folder, new() - { - /// - /// Sets the initial item values. - /// - /// The item. - /// The args. - protected override void SetInitialItemValues(TItemType item, ItemResolveArgs args) - { - base.SetInitialItemValues(item, args); - - item.IsRoot = args.Parent == null; - } - } } diff --git a/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs new file mode 100644 index 00000000000..f109a5e9a2d --- /dev/null +++ b/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs @@ -0,0 +1,27 @@ +#nullable disable + +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; + +namespace Emby.Server.Implementations.Library.Resolvers +{ + /// + /// Class FolderResolver. + /// + /// The type of the T item type. + public abstract class GenericFolderResolver : ItemResolver + where TItemType : Folder, new() + { + /// + /// Sets the initial item values. + /// + /// The item. + /// The args. + protected override void SetInitialItemValues(TItemType item, ItemResolveArgs args) + { + base.SetInitialItemValues(item, args); + + item.IsRoot = args.Parent == null; + } + } +} diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs index 69d71d0d9a0..e7abe1e6da5 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs @@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies /// /// Class BoxSetResolver. /// - public class BoxSetResolver : FolderResolver + public class BoxSetResolver : GenericFolderResolver { /// /// Resolves the specified args. diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs index 534bc80ddc8..1c560e8a6e4 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs @@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Library.Resolvers /// /// Class PhotoAlbumResolver. /// - public class PhotoAlbumResolver : FolderResolver + public class PhotoAlbumResolver : GenericFolderResolver { private readonly IImageProcessor _imageProcessor; private readonly ILibraryManager _libraryManager; diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs index 2c4ead71988..8ce59717d05 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Resolvers /// /// for library items. /// - public class PlaylistResolver : FolderResolver + public class PlaylistResolver : GenericFolderResolver { private string[] _musicPlaylistCollectionTypes = { diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs index a42ac41446f..6bb99964153 100644 --- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs @@ -13,7 +13,7 @@ using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.Library.Resolvers { - public class SpecialFolderResolver : FolderResolver + public class SpecialFolderResolver : GenericFolderResolver { private readonly IFileSystem _fileSystem; private readonly IServerApplicationPaths _appPaths; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index 7d707df182c..063f67543f6 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV /// /// Class SeasonResolver. /// - public class SeasonResolver : FolderResolver + public class SeasonResolver : GenericFolderResolver { private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localization; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 4d8a6494c7a..b7fbe01c5ae 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -18,7 +18,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV /// /// Class SeriesResolver. /// - public class SeriesResolver : FolderResolver + public class SeriesResolver : GenericFolderResolver { private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs index 912e680dd7e..4e9efc60f44 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs @@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the aspect. /// [JsonPropertyName("aspect")] - public string aspect { get; set; } + public string Aspect { get; set; } /// /// Gets or sets the category. diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs index 12f3576c65b..c37f19678f1 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs @@ -6,62 +6,62 @@ using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos { /// - /// Station dto. - /// - public class StationDto - { - /// - /// Gets or sets the station id. - /// - [JsonPropertyName("stationID")] - public string StationId { get; set; } + /// Station dto. + /// + public class StationDto + { + /// + /// Gets or sets the station id. + /// + [JsonPropertyName("stationID")] + public string StationId { get; set; } - /// - /// Gets or sets the name. - /// - [JsonPropertyName("name")] - public string Name { get; set; } + /// + /// Gets or sets the name. + /// + [JsonPropertyName("name")] + public string Name { get; set; } - /// - /// Gets or sets the callsign. - /// - [JsonPropertyName("callsign")] - public string Callsign { get; set; } + /// + /// Gets or sets the callsign. + /// + [JsonPropertyName("callsign")] + public string Callsign { get; set; } - /// - /// Gets or sets the broadcast language. - /// - [JsonPropertyName("broadcastLanguage")] - public List BroadcastLanguage { get; set; } + /// + /// Gets or sets the broadcast language. + /// + [JsonPropertyName("broadcastLanguage")] + public List BroadcastLanguage { get; set; } - /// - /// Gets or sets the description language. - /// - [JsonPropertyName("descriptionLanguage")] - public List DescriptionLanguage { get; set; } + /// + /// Gets or sets the description language. + /// + [JsonPropertyName("descriptionLanguage")] + public List DescriptionLanguage { get; set; } - /// - /// Gets or sets the broadcaster. - /// - [JsonPropertyName("broadcaster")] - public BroadcasterDto Broadcaster { get; set; } + /// + /// Gets or sets the broadcaster. + /// + [JsonPropertyName("broadcaster")] + public BroadcasterDto Broadcaster { get; set; } - /// - /// Gets or sets the affiliate. - /// - [JsonPropertyName("affiliate")] - public string Affiliate { get; set; } + /// + /// Gets or sets the affiliate. + /// + [JsonPropertyName("affiliate")] + public string Affiliate { get; set; } - /// - /// Gets or sets the logo. - /// - [JsonPropertyName("logo")] - public LogoDto Logo { get; set; } + /// + /// Gets or sets the logo. + /// + [JsonPropertyName("logo")] + public LogoDto Logo { get; set; } - /// - /// Gets or set a value indicating whether it is commercial free. - /// - [JsonPropertyName("isCommercialFree")] - public bool? IsCommercialFree { get; set; } - } + /// + /// Gets or sets a value indicating whether it is commercial free. + /// + [JsonPropertyName("isCommercialFree")] + public bool? IsCommercialFree { get; set; } + } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs new file mode 100644 index 00000000000..069b4fab6b4 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs @@ -0,0 +1,35 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; + +namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun +{ + public class HdHomerunChannelCommands : IHdHomerunChannelCommands + { + private string? _channel; + private string? _profile; + + public HdHomerunChannelCommands(string? channel, string? profile) + { + _channel = channel; + _profile = profile; + } + + public IEnumerable<(string, string)> GetCommands() + { + if (!string.IsNullOrEmpty(_channel)) + { + if (!string.IsNullOrEmpty(_profile) + && !string.Equals(_profile, "native", StringComparison.OrdinalIgnoreCase)) + { + yield return ("vchannel", $"{_channel} transcode={_profile}"); + } + else + { + yield return ("vchannel", _channel); + } + } + } + } +} diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 4d538c6043d..78ea7bd0f5f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -87,11 +87,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return lineup.Where(i => !i.DRM).ToList(); } - private class HdHomerunChannelInfo : ChannelInfo - { - public bool IsLegacyTuner { get; set; } - } - protected override async Task> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken) { var lineup = await GetLineup(tuner, cancellationToken).ConfigureAwait(false); @@ -715,5 +710,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return hostInfo; } + + private class HdHomerunChannelInfo : ChannelInfo + { + public bool IsLegacyTuner { get; set; } + } } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index b2e555c7dc8..f0f61297f6d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -5,12 +5,10 @@ using System; using System.Buffers; using System.Buffers.Binary; -using System.Collections.Generic; using System.Globalization; using System.Net; using System.Net.Sockets; using System.Text; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; @@ -18,70 +16,6 @@ using MediaBrowser.Controller.LiveTv; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { - public interface IHdHomerunChannelCommands - { - IEnumerable<(string, string)> GetCommands(); - } - - public class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands - { - private string _channel; - private string _program; - - public LegacyHdHomerunChannelCommands(string url) - { - // parse url for channel and program - var regExp = new Regex(@"\/ch([0-9]+)-?([0-9]*)"); - var match = regExp.Match(url); - if (match.Success) - { - _channel = match.Groups[1].Value; - _program = match.Groups[2].Value; - } - } - - public IEnumerable<(string, string)> GetCommands() - { - if (!string.IsNullOrEmpty(_channel)) - { - yield return ("channel", _channel); - } - - if (!string.IsNullOrEmpty(_program)) - { - yield return ("program", _program); - } - } - } - - public class HdHomerunChannelCommands : IHdHomerunChannelCommands - { - private string _channel; - private string _profile; - - public HdHomerunChannelCommands(string channel, string profile) - { - _channel = channel; - _profile = profile; - } - - public IEnumerable<(string, string)> GetCommands() - { - if (!string.IsNullOrEmpty(_channel)) - { - if (!string.IsNullOrEmpty(_profile) - && !string.Equals(_profile, "native", StringComparison.OrdinalIgnoreCase)) - { - yield return ("vchannel", $"{_channel} transcode={_profile}"); - } - else - { - yield return ("vchannel", _channel); - } - } - } - } - public sealed class HdHomerunManager : IDisposable { public const int HdHomeRunPort = 65001; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs new file mode 100644 index 00000000000..1533549320f --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs @@ -0,0 +1,11 @@ +#pragma warning disable CS1591 + +using System.Collections.Generic; + +namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun +{ + public interface IHdHomerunChannelCommands + { + IEnumerable<(string, string)> GetCommands(); + } +} diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs new file mode 100644 index 00000000000..26627b8aa68 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs @@ -0,0 +1,38 @@ +#pragma warning disable CS1591 + +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun +{ + public class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands + { + private string? _channel; + private string? _program; + + public LegacyHdHomerunChannelCommands(string url) + { + // parse url for channel and program + var regExp = new Regex(@"\/ch([0-9]+)-?([0-9]*)"); + var match = regExp.Match(url); + if (match.Success) + { + _channel = match.Groups[1].Value; + _program = match.Groups[2].Value; + } + } + + public IEnumerable<(string, string)> GetCommands() + { + if (!string.IsNullOrEmpty(_channel)) + { + yield return ("channel", _channel); + } + + if (!string.IsNullOrEmpty(_program)) + { + yield return ("program", _program); + } + } + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 47cec7d77f3..4b3a75b90b5 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -39,11 +39,6 @@ true true snupkg - false - - - - true diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index b0a12a9c900..91803ade6ea 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -21,11 +21,6 @@ true true snupkg - false - - - - true From f7ae3c6a858ccc8489e222492351461f1134a058 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 6 Oct 2021 11:39:54 +0200 Subject: [PATCH 186/278] Set AnalysisMode to AllEnabledByDefault --- Emby.Dlna/Emby.Dlna.csproj | 1 - Emby.Drawing/Emby.Drawing.csproj | 1 - Emby.Naming/Emby.Naming.csproj | 1 - Jellyfin.Api/Jellyfin.Api.csproj | 1 - 4 files changed, 4 deletions(-) diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index 1d4e3b047d3..8545c020f49 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -20,7 +20,6 @@ net6.0 false true - AllDisabledByDefault diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj index 300eea9680a..149e4b5d912 100644 --- a/Emby.Drawing/Emby.Drawing.csproj +++ b/Emby.Drawing/Emby.Drawing.csproj @@ -9,7 +9,6 @@ net6.0 false true - AllDisabledByDefault diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index 96f8f389b37..e9e9edda65e 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -13,7 +13,6 @@ true true snupkg - AllDisabledByDefault diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 1c451ef6c19..64f848ff72e 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -10,7 +10,6 @@ true AD0001 - AllDisabledByDefault From 6297fa5bf70880a2254f226d33da658b6b4ff05a Mon Sep 17 00:00:00 2001 From: Systerm Date: Wed, 6 Oct 2021 20:50:46 +0000 Subject: [PATCH 187/278] Translated using Weblate (Portuguese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt/ --- .../Localization/Core/pt.json | 75 ++++++++++--------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json index 474dacd7cc3..a9dbd53ea21 100644 --- a/Emby.Server.Implementations/Localization/Core/pt.json +++ b/Emby.Server.Implementations/Localization/Core/pt.json @@ -1,6 +1,6 @@ { - "HeaderLiveTV": "TV em Directo", - "Collections": "Colecções", + "HeaderLiveTV": "TV Ao Vivo", + "Collections": "Coleções", "Books": "Livros", "Artists": "Artistas", "Albums": "Álbuns", @@ -10,29 +10,29 @@ "HeaderFavoriteAlbums": "Álbuns Favoritos", "HeaderFavoriteEpisodes": "Episódios Favoritos", "HeaderFavoriteShows": "Séries Favoritas", - "HeaderContinueWatching": "Continuar a Assistir", + "HeaderContinueWatching": "Continuar assistindo", "HeaderAlbumArtists": "Artistas do Álbum", - "Genres": "Géneros", - "Folders": "Directórios", + "Genres": "Gêneros", + "Folders": "Diretórios", "Favorites": "Favoritos", "Channels": "Canais", - "UserDownloadingItemWithValues": "{0} está a ser transferido {1}", + "UserDownloadingItemWithValues": "{0} está sendo baixado {1}", "VersionNumber": "Versão {0}", "ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca multimédia", "UserStoppedPlayingItemWithValues": "{0} terminou a reprodução de {1} em {2}", - "UserStartedPlayingItemWithValues": "{0} está a reproduzir {1} em {2}", - "UserPolicyUpdatedWithName": "A política do utilizador {0} foi alterada", - "UserPasswordChangedWithName": "A palavra-passe do utilizador {0} foi alterada", - "UserOnlineFromDevice": "{0} ligou-se a partir de {1}", + "UserStartedPlayingItemWithValues": "{0} está reproduzindo {1} em {2}", + "UserPolicyUpdatedWithName": "A política do usuário {0} foi alterada", + "UserPasswordChangedWithName": "A senha do usuário {0} foi alterada", + "UserOnlineFromDevice": "{0} está online a partir de {1}", "UserOfflineFromDevice": "{0} desconectou-se a partir de {1}", - "UserLockedOutWithName": "O utilizador {0} foi bloqueado", - "UserDeletedWithName": "O utilizador {0} foi removido", - "UserCreatedWithName": "O utilizador {0} foi criado", - "User": "Utilizador", + "UserLockedOutWithName": "O usuário {0} foi bloqueado", + "UserDeletedWithName": "O usuário {0} foi removido", + "UserCreatedWithName": "O usuário {0} foi criado", + "User": "Usuário", "TvShows": "Séries", "System": "Sistema", "SubtitleDownloadFailureFromForItem": "Falha na transferência de legendas de {0} para {1}", - "StartupEmbyServerIsLoading": "O servidor Jellyfin está a iniciar. Tente novamente dentro de momentos.", + "StartupEmbyServerIsLoading": "O servidor Jellyfin está iniciando. Tente novamente dentro de momentos.", "ServerNameNeedsToBeRestarted": "{0} necessita ser reiniciado", "ScheduledTaskStartedWithName": "{0} iniciou", "ScheduledTaskFailedWithName": "{0} falhou", @@ -43,38 +43,38 @@ "Plugin": "Plugin", "NotificationOptionVideoPlaybackStopped": "Reprodução de vídeo parada", "NotificationOptionVideoPlayback": "Reprodução de vídeo iniciada", - "NotificationOptionUserLockedOut": "Utilizador bloqueado", - "NotificationOptionTaskFailed": "Falha em tarefa agendada", + "NotificationOptionUserLockedOut": "Usuário bloqueado", + "NotificationOptionTaskFailed": "Falha na tarefa agendada", "NotificationOptionServerRestartRequired": "É necessário reiniciar o servidor", - "NotificationOptionPluginUpdateInstalled": "Plugin actualizado", + "NotificationOptionPluginUpdateInstalled": "Plugin atualizado", "NotificationOptionPluginUninstalled": "Plugin desinstalado", "NotificationOptionPluginInstalled": "Plugin instalado", "NotificationOptionPluginError": "Falha no plugin", "NotificationOptionNewLibraryContent": "Novo conteúdo adicionado", "NotificationOptionInstallationFailed": "Falha de instalação", - "NotificationOptionCameraImageUploaded": "Imagem de câmara enviada", + "NotificationOptionCameraImageUploaded": "Imagem de câmera enviada", "NotificationOptionAudioPlaybackStopped": "Reprodução Parada", "NotificationOptionAudioPlayback": "Reprodução Iniciada", - "NotificationOptionApplicationUpdateInstalled": "A actualização da aplicação foi instalada", - "NotificationOptionApplicationUpdateAvailable": "Uma actualização da aplicação está disponível", - "NewVersionIsAvailable": "Uma nova versão do servidor Jellyfin está disponível para transferência.", + "NotificationOptionApplicationUpdateInstalled": "A atualização do aplicativo foi instalada", + "NotificationOptionApplicationUpdateAvailable": "Uma atualização do aplicativo está disponível", + "NewVersionIsAvailable": "Uma nova versão do servidor Jellyfin está disponível para download.", "NameSeasonUnknown": "Temporada Desconhecida", "NameSeasonNumber": "Temporada {0}", "NameInstallFailed": "Falha na instalação de {0}", "MusicVideos": "Videoclipes", "Music": "Música", - "MixedContent": "Conteúdo Misto", - "MessageServerConfigurationUpdated": "A configuração do servidor foi actualizada", - "MessageNamedServerConfigurationUpdatedWithValue": "As configurações do servidor na secção {0} foram atualizadas", + "MixedContent": "Conteúdo diverso", + "MessageServerConfigurationUpdated": "A configuração do servidor foi atualizada", + "MessageNamedServerConfigurationUpdatedWithValue": "As configurações do servidor na seção {0} foram atualizadas", "MessageApplicationUpdatedTo": "O servidor Jellyfin foi atualizado para a versão {0}", - "MessageApplicationUpdated": "O servidor Jellyfin foi actualizado", + "MessageApplicationUpdated": "O servidor Jellyfin foi atualizado", "Latest": "Mais Recente", "LabelRunningTimeValue": "Duração: {0}", "LabelIpAddressValue": "Endereço de IP: {0}", "ItemRemovedWithName": "{0} foi removido da biblioteca", "ItemAddedWithName": "{0} foi adicionado à biblioteca", "Inherit": "Herdar", - "HomeVideos": "Vídeos Caseiros", + "HomeVideos": "Vídeos principais", "HeaderRecordingGroups": "Grupos de Gravação", "ValueSpecialEpisodeName": "Episódio Especial - {0}", "Sync": "Sincronização", @@ -83,22 +83,22 @@ "Playlists": "Listas de Reprodução", "Photos": "Fotografias", "Movies": "Filmes", - "FailedLoginAttemptWithUserName": "Tentativa de ligação falhada a partir de {0}", - "DeviceOnlineWithName": "{0} está connectado", + "FailedLoginAttemptWithUserName": "Tentativa falha de login a partir de {0}", + "DeviceOnlineWithName": "{0} está conectado", "DeviceOfflineWithName": "{0} desconectou-se", "ChapterNameValue": "Capítulo {0}", "CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}", "AuthenticationSucceededWithUserName": "{0} autenticado com sucesso", - "Application": "Aplicação", - "AppDeviceValues": "Aplicação {0}, Dispositivo: {1}", + "Application": "Aplicativo", + "AppDeviceValues": "Aplicativo {0}, Dispositivo: {1}", "TaskCleanCache": "Limpar Diretório de Cache", - "TasksApplicationCategory": "Aplicação", + "TasksApplicationCategory": "Aplicativo", "TasksLibraryCategory": "Biblioteca", "TasksMaintenanceCategory": "Manutenção", "TaskRefreshChannels": "Atualizar Canais", "TaskUpdatePlugins": "Atualizar Plugins", "TaskCleanLogsDescription": "Deletar arquivos de log que existe a mais de {0} dias.", - "TaskCleanLogs": "Limpar diretório de log", + "TaskCleanLogs": "Limpar diretório de logs", "TaskRefreshLibrary": "Escanear biblioteca de mídias", "TaskRefreshChapterImagesDescription": "Cria miniaturas para vídeos que têm capítulos.", "TaskCleanCacheDescription": "Apaga ficheiros em cache que já não são usados pelo sistema.", @@ -109,14 +109,15 @@ "TaskRefreshChannelsDescription": "Atualiza as informações do canal da Internet.", "TaskCleanTranscodeDescription": "Apagar os ficheiros com mais de um dia, de Transcode.", "TaskCleanTranscode": "Limpar o diretório de Transcode", - "TaskUpdatePluginsDescription": "Download e instala as atualizações para plug-ins configurados para atualização automática.", + "TaskUpdatePluginsDescription": "Baixa e instala as atualizações para plug-ins configurados para atualização automática.", "TaskRefreshPeopleDescription": "Atualiza os metadados para atores e diretores na tua biblioteca de media.", "TaskRefreshPeople": "Atualizar pessoas", - "TaskRefreshLibraryDescription": "Pesquisa a tua biblioteca de media por novos ficheiros e atualiza os metadados.", - "TaskCleanActivityLog": "Limpar registo de atividade", + "TaskRefreshLibraryDescription": "Pesquisa sua biblioteca de media por novos arquivos e atualiza os metadados.", + "TaskCleanActivityLog": "Limpar registro de atividade", "Undefined": "Indefinido", "Forced": "Forçado", "Default": "Predefinição", "TaskCleanActivityLogDescription": "Apaga itens no registro com idade acima do que é configurado.", - "TaskOptimizeDatabase": "Otimizar base de dados" + "TaskOptimizeDatabase": "Otimizar base de dados", + "TaskOptimizeDatabaseDescription": "Base de dados compacta e corta espaço livre. A execução desta tarefa depois de digitalizar a biblioteca ou de fazer outras alterações que impliquem modificações na base de dados pode melhorar o desempenho." } From 3add805cbfe5b0ca3e45172529dbd41a4dc461f7 Mon Sep 17 00:00:00 2001 From: cvium Date: Thu, 7 Oct 2021 23:20:54 +0200 Subject: [PATCH 188/278] Return the path to the pinfile in forgot password --- .../Users/DefaultPasswordResetProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs index 6e98ad8630e..25ef01dce9a 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -123,6 +123,7 @@ namespace Jellyfin.Server.Implementations.Users { Action = ForgotPasswordAction.PinCode, PinExpirationDate = expireTime, + PinFile = filePath }; } From 3b492d4af8c432cc11b11e946b72aaf97cf63c95 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 8 Oct 2021 15:02:58 +0200 Subject: [PATCH 189/278] Use static crypto rng --- .../Cryptography/CryptographyProvider.cs | 54 +++---------------- .../Users/DefaultPasswordResetProvider.cs | 11 ++-- 2 files changed, 9 insertions(+), 56 deletions(-) diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 4a9b2808524..673810c49d9 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -10,8 +10,12 @@ namespace Emby.Server.Implementations.Cryptography /// /// Class providing abstractions over cryptographic functions. /// - public class CryptographyProvider : ICryptoProvider, IDisposable + public class CryptographyProvider : ICryptoProvider { + // FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto + // Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 + // there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one + // Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1 private static readonly HashSet _supportedHashMethods = new HashSet() { "MD5", @@ -30,22 +34,6 @@ namespace Emby.Server.Implementations.Cryptography "System.Security.Cryptography.SHA512" }; - private RandomNumberGenerator _randomNumberGenerator; - - private bool _disposed; - - /// - /// Initializes a new instance of the class. - /// - public CryptographyProvider() - { - // FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto - // Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 - // there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one - // Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1 - _randomNumberGenerator = RandomNumberGenerator.Create(); - } - /// public string DefaultHashMethod => "PBKDF2"; @@ -101,36 +89,6 @@ namespace Emby.Server.Implementations.Cryptography /// public byte[] GenerateSalt(int length) - { - byte[] salt = new byte[length]; - _randomNumberGenerator.GetBytes(salt); - return salt; - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - _randomNumberGenerator.Dispose(); - } - - _disposed = true; - } + => RandomNumberGenerator.GetBytes(length); } } diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs index 25ef01dce9a..5e84255f914 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -93,13 +93,9 @@ namespace Jellyfin.Server.Implementations.Users /// public async Task StartForgotPasswordProcess(User user, bool isInNetwork) { - string pin; - using (var cryptoRandom = RandomNumberGenerator.Create()) - { - byte[] bytes = new byte[4]; - cryptoRandom.GetBytes(bytes); - pin = BitConverter.ToString(bytes); - } + byte[] bytes = new byte[4]; + RandomNumberGenerator.Fill(bytes); + string pin = BitConverter.ToString(bytes); DateTime expireTime = DateTime.UtcNow.AddMinutes(30); string filePath = _passwordResetFileBase + user.Id + ".json"; @@ -114,7 +110,6 @@ namespace Jellyfin.Server.Implementations.Users await using (FileStream fileStream = AsyncFile.OpenWrite(filePath)) { await JsonSerializer.SerializeAsync(fileStream, spr).ConfigureAwait(false); - await fileStream.FlushAsync().ConfigureAwait(false); } user.EasyPassword = pin; From dc8feca6bb09a1af0dd8a934f7249602a0befabf Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 8 Oct 2021 15:20:11 +0200 Subject: [PATCH 190/278] =?UTF-8?q?Remove=20duplicate=20Fisher=E2=80=93Yat?= =?UTF-8?q?es=20shuffle=20impl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SyncPlay/Queue/PlayQueueManager.cs | 124 +++++++----------- src/Jellyfin.Extensions/ShuffleExtensions.cs | 4 +- 2 files changed, 52 insertions(+), 76 deletions(-) diff --git a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs index b8ae9f3ff67..f49876cca4f 100644 --- a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs +++ b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Extensions; using MediaBrowser.Model.SyncPlay; namespace MediaBrowser.Controller.SyncPlay.Queue @@ -19,10 +20,16 @@ namespace MediaBrowser.Controller.SyncPlay.Queue private const int NoPlayingItemIndex = -1; /// - /// Random number generator used to shuffle lists. + /// The sorted playlist. /// - /// The random number generator. - private readonly Random _randomNumberGenerator = new Random(); + /// The sorted playlist, or play queue of the group. + private List _sortedPlaylist = new List(); + + /// + /// The shuffled playlist. + /// + /// The shuffled playlist, or play queue of the group. + private List _shuffledPlaylist = new List(); /// /// Initializes a new instance of the class. @@ -56,18 +63,6 @@ namespace MediaBrowser.Controller.SyncPlay.Queue /// The repeat mode. public GroupRepeatMode RepeatMode { get; private set; } = GroupRepeatMode.RepeatNone; - /// - /// Gets or sets the sorted playlist. - /// - /// The sorted playlist, or play queue of the group. - private List SortedPlaylist { get; set; } = new List(); - - /// - /// Gets or sets the shuffled playlist. - /// - /// The shuffled playlist, or play queue of the group. - private List ShuffledPlaylist { get; set; } = new List(); - /// /// Checks if an item is playing. /// @@ -92,14 +87,14 @@ namespace MediaBrowser.Controller.SyncPlay.Queue /// The new items of the playlist. public void SetPlaylist(IReadOnlyList items) { - SortedPlaylist.Clear(); - ShuffledPlaylist.Clear(); + _sortedPlaylist.Clear(); + _shuffledPlaylist.Clear(); - SortedPlaylist = CreateQueueItemsFromArray(items); + _sortedPlaylist = CreateQueueItemsFromArray(items); if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { - ShuffledPlaylist = new List(SortedPlaylist); - Shuffle(ShuffledPlaylist); + _shuffledPlaylist = new List(_sortedPlaylist); + _shuffledPlaylist.Shuffle(); } PlayingItemIndex = NoPlayingItemIndex; @@ -114,10 +109,10 @@ namespace MediaBrowser.Controller.SyncPlay.Queue { var newItems = CreateQueueItemsFromArray(items); - SortedPlaylist.AddRange(newItems); + _sortedPlaylist.AddRange(newItems); if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { - ShuffledPlaylist.AddRange(newItems); + _shuffledPlaylist.AddRange(newItems); } LastChange = DateTime.UtcNow; @@ -130,26 +125,26 @@ namespace MediaBrowser.Controller.SyncPlay.Queue { if (PlayingItemIndex == NoPlayingItemIndex) { - ShuffledPlaylist = new List(SortedPlaylist); - Shuffle(ShuffledPlaylist); + _shuffledPlaylist = new List(_sortedPlaylist); + _shuffledPlaylist.Shuffle(); } else if (ShuffleMode.Equals(GroupShuffleMode.Sorted)) { // First time shuffle. - var playingItem = SortedPlaylist[PlayingItemIndex]; - ShuffledPlaylist = new List(SortedPlaylist); - ShuffledPlaylist.RemoveAt(PlayingItemIndex); - Shuffle(ShuffledPlaylist); - ShuffledPlaylist.Insert(0, playingItem); + var playingItem = _sortedPlaylist[PlayingItemIndex]; + _shuffledPlaylist = new List(_sortedPlaylist); + _shuffledPlaylist.RemoveAt(PlayingItemIndex); + _shuffledPlaylist.Shuffle(); + _shuffledPlaylist.Insert(0, playingItem); PlayingItemIndex = 0; } else { // Re-shuffle playlist. - var playingItem = ShuffledPlaylist[PlayingItemIndex]; - ShuffledPlaylist.RemoveAt(PlayingItemIndex); - Shuffle(ShuffledPlaylist); - ShuffledPlaylist.Insert(0, playingItem); + var playingItem = _shuffledPlaylist[PlayingItemIndex]; + _shuffledPlaylist.RemoveAt(PlayingItemIndex); + _shuffledPlaylist.Shuffle(); + _shuffledPlaylist.Insert(0, playingItem); PlayingItemIndex = 0; } @@ -164,11 +159,11 @@ namespace MediaBrowser.Controller.SyncPlay.Queue { if (PlayingItemIndex != NoPlayingItemIndex) { - var playingItem = ShuffledPlaylist[PlayingItemIndex]; - PlayingItemIndex = SortedPlaylist.IndexOf(playingItem); + var playingItem = _shuffledPlaylist[PlayingItemIndex]; + PlayingItemIndex = _sortedPlaylist.IndexOf(playingItem); } - ShuffledPlaylist.Clear(); + _shuffledPlaylist.Clear(); ShuffleMode = GroupShuffleMode.Sorted; LastChange = DateTime.UtcNow; @@ -181,16 +176,16 @@ namespace MediaBrowser.Controller.SyncPlay.Queue public void ClearPlaylist(bool clearPlayingItem) { var playingItem = GetPlayingItem(); - SortedPlaylist.Clear(); - ShuffledPlaylist.Clear(); + _sortedPlaylist.Clear(); + _shuffledPlaylist.Clear(); LastChange = DateTime.UtcNow; if (!clearPlayingItem && playingItem != null) { - SortedPlaylist.Add(playingItem); + _sortedPlaylist.Add(playingItem); if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { - ShuffledPlaylist.Add(playingItem); + _shuffledPlaylist.Add(playingItem); } PlayingItemIndex = 0; @@ -212,14 +207,14 @@ namespace MediaBrowser.Controller.SyncPlay.Queue if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { var playingItem = GetPlayingItem(); - var sortedPlayingItemIndex = SortedPlaylist.IndexOf(playingItem); + var sortedPlayingItemIndex = _sortedPlaylist.IndexOf(playingItem); // Append items to sorted and shuffled playlist as they are. - SortedPlaylist.InsertRange(sortedPlayingItemIndex + 1, newItems); - ShuffledPlaylist.InsertRange(PlayingItemIndex + 1, newItems); + _sortedPlaylist.InsertRange(sortedPlayingItemIndex + 1, newItems); + _shuffledPlaylist.InsertRange(PlayingItemIndex + 1, newItems); } else { - SortedPlaylist.InsertRange(PlayingItemIndex + 1, newItems); + _sortedPlaylist.InsertRange(PlayingItemIndex + 1, newItems); } LastChange = DateTime.UtcNow; @@ -298,8 +293,8 @@ namespace MediaBrowser.Controller.SyncPlay.Queue { var playingItem = GetPlayingItem(); - SortedPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId)); - ShuffledPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId)); + _sortedPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId)); + _shuffledPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId)); LastChange = DateTime.UtcNow; @@ -313,7 +308,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue { // Was first element, picking next if available. // Default to no playing item otherwise. - PlayingItemIndex = SortedPlaylist.Count > 0 ? 0 : NoPlayingItemIndex; + PlayingItemIndex = _sortedPlaylist.Count > 0 ? 0 : NoPlayingItemIndex; } return true; @@ -363,8 +358,8 @@ namespace MediaBrowser.Controller.SyncPlay.Queue /// public void Reset() { - SortedPlaylist.Clear(); - ShuffledPlaylist.Clear(); + _sortedPlaylist.Clear(); + _shuffledPlaylist.Clear(); PlayingItemIndex = NoPlayingItemIndex; ShuffleMode = GroupShuffleMode.Sorted; RepeatMode = GroupRepeatMode.RepeatNone; @@ -460,7 +455,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue } PlayingItemIndex++; - if (PlayingItemIndex >= SortedPlaylist.Count) + if (PlayingItemIndex >= _sortedPlaylist.Count) { if (RepeatMode.Equals(GroupRepeatMode.RepeatAll)) { @@ -468,7 +463,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue } else { - PlayingItemIndex = SortedPlaylist.Count - 1; + PlayingItemIndex = _sortedPlaylist.Count - 1; return false; } } @@ -494,7 +489,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue { if (RepeatMode.Equals(GroupRepeatMode.RepeatAll)) { - PlayingItemIndex = SortedPlaylist.Count - 1; + PlayingItemIndex = _sortedPlaylist.Count - 1; } else { @@ -507,23 +502,6 @@ namespace MediaBrowser.Controller.SyncPlay.Queue return true; } - /// - /// Shuffles a given list. - /// - /// The list to shuffle. - private void Shuffle(IList list) - { - int n = list.Count; - while (n > 1) - { - n--; - int k = _randomNumberGenerator.Next(n + 1); - T value = list[k]; - list[k] = list[n]; - list[n] = value; - } - } - /// /// Creates a list from the array of items. Each item is given an unique playlist identifier. /// @@ -548,11 +526,11 @@ namespace MediaBrowser.Controller.SyncPlay.Queue { if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { - return ShuffledPlaylist; + return _shuffledPlaylist; } else { - return SortedPlaylist; + return _sortedPlaylist; } } @@ -568,11 +546,11 @@ namespace MediaBrowser.Controller.SyncPlay.Queue } else if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { - return ShuffledPlaylist[PlayingItemIndex]; + return _shuffledPlaylist[PlayingItemIndex]; } else { - return SortedPlaylist[PlayingItemIndex]; + return _sortedPlaylist[PlayingItemIndex]; } } } diff --git a/src/Jellyfin.Extensions/ShuffleExtensions.cs b/src/Jellyfin.Extensions/ShuffleExtensions.cs index 4e481983f3e..33c492053a9 100644 --- a/src/Jellyfin.Extensions/ShuffleExtensions.cs +++ b/src/Jellyfin.Extensions/ShuffleExtensions.cs @@ -8,8 +8,6 @@ namespace Jellyfin.Extensions /// public static class ShuffleExtensions { - private static readonly Random _rng = new Random(); - /// /// Shuffles the items in a list. /// @@ -17,7 +15,7 @@ namespace Jellyfin.Extensions /// The type. public static void Shuffle(this IList list) { - list.Shuffle(_rng); + list.Shuffle(Random.Shared); } /// From d05062fec06ecba1049beefffe8d8f521d3e1881 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 8 Oct 2021 15:40:13 +0200 Subject: [PATCH 191/278] Use new Random.Shared instead of creating new instances --- Emby.Server.Implementations/Dto/DtoService.cs | 2 +- .../LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs | 3 +-- Jellyfin.Api/Controllers/MediaInfoController.cs | 2 +- tests/Jellyfin.Extensions.Tests/ShuffleExtensionsTests.cs | 4 +--- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 74400b51283..ad76f3d6d4c 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -420,7 +420,7 @@ namespace Emby.Server.Implementations.Dto // Just return something so that apps that are expecting a value won't think the folders are empty if (folder is ICollectionFolder || folder is UserView) { - return new Random().Next(1, 10); + return Random.Shared.Next(1, 10); } return folder.GetChildCount(user); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index b2e555c7dc8..08ed1a32acc 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -150,8 +150,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun if (!_lockkey.HasValue) { - var rand = new Random(); - _lockkey = (uint)rand.Next(); + _lockkey = (uint)Random.Shared.Next(); } var lockKeyValue = _lockkey.Value; diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs index 96ef2d678b2..b422eb78c69 100644 --- a/Jellyfin.Api/Controllers/MediaInfoController.cs +++ b/Jellyfin.Api/Controllers/MediaInfoController.cs @@ -316,7 +316,7 @@ namespace Jellyfin.Api.Controllers byte[] buffer = ArrayPool.Shared.Rent(size); try { - new Random().NextBytes(buffer); + Random.Shared.NextBytes(buffer); return File(buffer, MediaTypeNames.Application.Octet); } finally diff --git a/tests/Jellyfin.Extensions.Tests/ShuffleExtensionsTests.cs b/tests/Jellyfin.Extensions.Tests/ShuffleExtensionsTests.cs index c72216d94b8..a73cfb07816 100644 --- a/tests/Jellyfin.Extensions.Tests/ShuffleExtensionsTests.cs +++ b/tests/Jellyfin.Extensions.Tests/ShuffleExtensionsTests.cs @@ -5,13 +5,11 @@ namespace Jellyfin.Extensions.Tests { public static class ShuffleExtensionsTests { - private static readonly Random _rng = new Random(); - [Fact] public static void Shuffle_Valid_Correct() { byte[] original = new byte[1 << 6]; - _rng.NextBytes(original); + Random.Shared.NextBytes(original); byte[] shuffled = (byte[])original.Clone(); shuffled.Shuffle(); From fe461ff66bd69997014fe190745e44e7696d0167 Mon Sep 17 00:00:00 2001 From: Marcel <34819524+MarcelCoding@users.noreply.github.com> Date: Sat, 9 Oct 2021 08:21:01 +0000 Subject: [PATCH 192/278] Added Docker Healthcheck --- Dockerfile | 8 +++++++- Dockerfile.arm | 9 ++++++++- Dockerfile.arm64 | 8 ++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8ca3a516a2a..22041cdc4c0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,8 +29,9 @@ ARG LEVEL_ZERO_VERSION=1.2.20826 # Install dependencies: # mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding. +# curl: healcheck RUN apt-get update \ - && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https \ + && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https curl \ && wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \ && echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \ && apt-get update \ @@ -85,3 +86,8 @@ ENTRYPOINT ["./jellyfin/jellyfin", \ "--datadir", "/config", \ "--cachedir", "/cache", \ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"] + +HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ + # https://github.com/jellyfin/jellyfin/issues/5760#issuecomment-852297561 + CMD curl http://localhost:$(grep -oP '(?<=PublicPort>)[^<]+' /config/config/network.xml)/$(grep -oP '(?<=BaseUrl>)[^<]+' /config/config/network.xml)health \ + || exit 1 diff --git a/Dockerfile.arm b/Dockerfile.arm index fa68a5c0603..b80ad2632f7 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -24,6 +24,8 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin + +# curl: setup & healcheck RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \ curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \ @@ -42,7 +44,7 @@ RUN apt-get update \ vainfo \ libva2 \ locales \ - && apt-get remove curl gnupg -y \ + && apt-get remove gnupg -y \ && apt-get clean autoclean -y \ && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* \ @@ -75,3 +77,8 @@ ENTRYPOINT ["./jellyfin/jellyfin", \ "--datadir", "/config", \ "--cachedir", "/cache", \ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"] + +HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ + # https://github.com/jellyfin/jellyfin/issues/5760#issuecomment-852297561 + CMD curl http://localhost:$(grep -oP '(?<=PublicPort>)[^<]+' /config/config/network.xml)/$(grep -oP '(?<=BaseUrl>)[^<]+' /config/config/network.xml)health \ + || exit 1 diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 4ae98de3278..6ac91ce735d 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -24,6 +24,8 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin + +# curl: healcheck RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \ ffmpeg \ libssl-dev \ @@ -33,6 +35,7 @@ RUN apt-get update && apt-get install --no-install-recommends --no-install-sugge libomxil-bellagio0 \ libomxil-bellagio-bin \ locales \ + curl \ && apt-get clean autoclean -y \ && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* \ @@ -65,3 +68,8 @@ ENTRYPOINT ["./jellyfin/jellyfin", \ "--datadir", "/config", \ "--cachedir", "/cache", \ "--ffmpeg", "/usr/bin/ffmpeg"] + +HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ + # https://github.com/jellyfin/jellyfin/issues/5760#issuecomment-852297561 + CMD curl http://localhost:$(grep -oP '(?<=PublicPort>)[^<]+' /config/config/network.xml)/$(grep -oP '(?<=BaseUrl>)[^<]+' /config/config/network.xml)health \ + || exit 1 From a81dfabdb649b98e1a809dfee941b75637623e9e Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sat, 9 Oct 2021 07:16:50 -0600 Subject: [PATCH 193/278] Fix indentation and build errors --- .../Listings/SchedulesDirectDtos/MapDto.cs | 2 +- .../SchedulesDirectDtos/StationDto.cs | 100 +++++++++--------- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs index 2420307b4f9..ffd02d474b9 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs @@ -53,6 +53,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos /// Gets or sets the match type. /// [JsonPropertyName("matchType")] - public string? MatchType { get;set; } + public string? MatchType { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs index 1b76b22e32a..d797fd49b1f 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs @@ -5,62 +5,62 @@ using System.Text.Json.Serialization; namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos { /// - /// Station dto. - /// - public class StationDto - { - /// - /// Gets or sets the station id. - /// - [JsonPropertyName("stationID")] - public string? StationId { get; set; } + /// Station dto. + /// + public class StationDto + { + /// + /// Gets or sets the station id. + /// + [JsonPropertyName("stationID")] + public string? StationId { get; set; } - /// - /// Gets or sets the name. - /// - [JsonPropertyName("name")] - public string? Name { get; set; } + /// + /// Gets or sets the name. + /// + [JsonPropertyName("name")] + public string? Name { get; set; } - /// - /// Gets or sets the callsign. - /// - [JsonPropertyName("callsign")] - public string? Callsign { get; set; } + /// + /// Gets or sets the callsign. + /// + [JsonPropertyName("callsign")] + public string? Callsign { get; set; } - /// - /// Gets or sets the broadcast language. - /// - [JsonPropertyName("broadcastLanguage")] - public IReadOnlyList BroadcastLanguage { get; set; } = Array.Empty(); + /// + /// Gets or sets the broadcast language. + /// + [JsonPropertyName("broadcastLanguage")] + public IReadOnlyList BroadcastLanguage { get; set; } = Array.Empty(); - /// - /// Gets or sets the description language. - /// - [JsonPropertyName("descriptionLanguage")] - public IReadOnlyList DescriptionLanguage { get; set; } = Array.Empty(); + /// + /// Gets or sets the description language. + /// + [JsonPropertyName("descriptionLanguage")] + public IReadOnlyList DescriptionLanguage { get; set; } = Array.Empty(); - /// - /// Gets or sets the broadcaster. - /// - [JsonPropertyName("broadcaster")] - public BroadcasterDto? Broadcaster { get; set; } + /// + /// Gets or sets the broadcaster. + /// + [JsonPropertyName("broadcaster")] + public BroadcasterDto? Broadcaster { get; set; } - /// - /// Gets or sets the affiliate. - /// - [JsonPropertyName("affiliate")] - public string? Affiliate { get; set; } + /// + /// Gets or sets the affiliate. + /// + [JsonPropertyName("affiliate")] + public string? Affiliate { get; set; } - /// - /// Gets or sets the logo. - /// - [JsonPropertyName("logo")] - public LogoDto? Logo { get; set; } + /// + /// Gets or sets the logo. + /// + [JsonPropertyName("logo")] + public LogoDto? Logo { get; set; } - /// - /// Gets or set a value indicating whether it is commercial free. - /// - [JsonPropertyName("isCommercialFree")] - public bool? IsCommercialFree { get; set; } - } + /// + /// Gets or sets a value indicating whether it is commercial free. + /// + [JsonPropertyName("isCommercialFree")] + public bool? IsCommercialFree { get; set; } + } } From 32d6e7db7c510374b7164fecb4511e26a4e25ead Mon Sep 17 00:00:00 2001 From: Marcel <34819524+MarcelCoding@users.noreply.github.com> Date: Sat, 9 Oct 2021 17:00:20 +0200 Subject: [PATCH 194/278] Apply suggestions from code review Co-authored-by: Cody Robibero --- Dockerfile | 3 +-- Dockerfile.arm | 3 +-- Dockerfile.arm64 | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 22041cdc4c0..14d807176ec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ ARG LEVEL_ZERO_VERSION=1.2.20826 # Install dependencies: # mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding. -# curl: healcheck +# curl: healthcheck RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https curl \ && wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \ @@ -88,6 +88,5 @@ ENTRYPOINT ["./jellyfin/jellyfin", \ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"] HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ - # https://github.com/jellyfin/jellyfin/issues/5760#issuecomment-852297561 CMD curl http://localhost:$(grep -oP '(?<=PublicPort>)[^<]+' /config/config/network.xml)/$(grep -oP '(?<=BaseUrl>)[^<]+' /config/config/network.xml)health \ || exit 1 diff --git a/Dockerfile.arm b/Dockerfile.arm index b80ad2632f7..40aa9f1116e 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -25,7 +25,7 @@ ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin -# curl: setup & healcheck +# curl: setup & healthcheck RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \ curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \ @@ -79,6 +79,5 @@ ENTRYPOINT ["./jellyfin/jellyfin", \ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"] HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ - # https://github.com/jellyfin/jellyfin/issues/5760#issuecomment-852297561 CMD curl http://localhost:$(grep -oP '(?<=PublicPort>)[^<]+' /config/config/network.xml)/$(grep -oP '(?<=BaseUrl>)[^<]+' /config/config/network.xml)health \ || exit 1 diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 6ac91ce735d..d491e7e1eed 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -70,6 +70,5 @@ ENTRYPOINT ["./jellyfin/jellyfin", \ "--ffmpeg", "/usr/bin/ffmpeg"] HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ - # https://github.com/jellyfin/jellyfin/issues/5760#issuecomment-852297561 CMD curl http://localhost:$(grep -oP '(?<=PublicPort>)[^<]+' /config/config/network.xml)/$(grep -oP '(?<=BaseUrl>)[^<]+' /config/config/network.xml)health \ || exit 1 From 2b004e1f762c5e43c8321bc1aa9740d8102a0426 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sat, 9 Oct 2021 21:36:39 +0300 Subject: [PATCH 195/278] Add more sorting to Latest --- Emby.Server.Implementations/Library/UserViewManager.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index eda494815db..711647e7a6d 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -355,7 +355,12 @@ namespace Emby.Server.Implementations.Library var query = new InternalItemsQuery(user) { IncludeItemTypes = includeItemTypes, - OrderBy = new[] { (ItemSortBy.DateCreated, SortOrder.Descending) }, + OrderBy = new[] + { + (ItemSortBy.DateCreated, SortOrder.Descending), + (ItemSortBy.SortName, SortOrder.Descending), + (ItemSortBy.ProductionYear, SortOrder.Descending) + }, IsFolder = includeItemTypes.Length == 0 ? false : (bool?)null, ExcludeItemTypes = excludeItemTypes, IsVirtualItem = false, From 5d11fd68c6eb62da3aa9d5052653b8c32a7a7c80 Mon Sep 17 00:00:00 2001 From: Marcel <34819524+MarcelCoding@users.noreply.github.com> Date: Sat, 9 Oct 2021 21:41:46 +0200 Subject: [PATCH 196/278] Apply suggestions from code review Co-authored-by: Cody Robibero --- Dockerfile | 2 +- Dockerfile.arm | 2 +- Dockerfile.arm64 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 14d807176ec..c4acf3c08dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -88,5 +88,5 @@ ENTRYPOINT ["./jellyfin/jellyfin", \ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"] HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ - CMD curl http://localhost:$(grep -oP '(?<=PublicPort>)[^<]+' /config/config/network.xml)/$(grep -oP '(?<=BaseUrl>)[^<]+' /config/config/network.xml)health \ + CMD curl http://localhost:$(grep -oP '(?<=HttpServerPortNumber>)[^<]+' /config/config/network.xml)/$(grep -oP '(?<=BaseUrl>)[^<]+' /config/config/network.xml)health \ || exit 1 diff --git a/Dockerfile.arm b/Dockerfile.arm index 40aa9f1116e..f9d249cc174 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -79,5 +79,5 @@ ENTRYPOINT ["./jellyfin/jellyfin", \ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"] HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ - CMD curl http://localhost:$(grep -oP '(?<=PublicPort>)[^<]+' /config/config/network.xml)/$(grep -oP '(?<=BaseUrl>)[^<]+' /config/config/network.xml)health \ + CMD curl http://localhost:$(grep -oP '(?<=HttpServerPortNumber>)[^<]+' /config/config/network.xml)/$(grep -oP '(?<=BaseUrl>)[^<]+' /config/config/network.xml)health \ || exit 1 diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index d491e7e1eed..cf1a392eaa6 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -70,5 +70,5 @@ ENTRYPOINT ["./jellyfin/jellyfin", \ "--ffmpeg", "/usr/bin/ffmpeg"] HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ - CMD curl http://localhost:$(grep -oP '(?<=PublicPort>)[^<]+' /config/config/network.xml)/$(grep -oP '(?<=BaseUrl>)[^<]+' /config/config/network.xml)health \ + CMD curl http://localhost:$(grep -oP '(?<=HttpServerPortNumber>)[^<]+' /config/config/network.xml)/$(grep -oP '(?<=BaseUrl>)[^<]+' /config/config/network.xml)health \ || exit 1 From aa93774b29193680799979c9c94bbb5ae2316a8a Mon Sep 17 00:00:00 2001 From: TheGoose Date: Sat, 9 Oct 2021 10:10:41 +0000 Subject: [PATCH 197/278] Translated using Weblate (English (United Kingdom)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/en_GB/ --- Emby.Server.Implementations/Localization/Core/en-GB.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json index 8b2e8b6b1c0..86ce9240e54 100644 --- a/Emby.Server.Implementations/Localization/Core/en-GB.json +++ b/Emby.Server.Implementations/Localization/Core/en-GB.json @@ -15,7 +15,7 @@ "Favorites": "Favourites", "Folders": "Folders", "Genres": "Genres", - "HeaderAlbumArtists": "Album Artists", + "HeaderAlbumArtists": "Artist's Album", "HeaderContinueWatching": "Continue Watching", "HeaderFavoriteAlbums": "Favourite Albums", "HeaderFavoriteArtists": "Favourite Artists", @@ -25,7 +25,7 @@ "HeaderLiveTV": "Live TV", "HeaderNextUp": "Next Up", "HeaderRecordingGroups": "Recording Groups", - "HomeVideos": "Home videos", + "HomeVideos": "Home Videos", "Inherit": "Inherit", "ItemAddedWithName": "{0} was added to the library", "ItemRemovedWithName": "{0} was removed from the library", @@ -39,7 +39,7 @@ "MixedContent": "Mixed content", "Movies": "Movies", "Music": "Music", - "MusicVideos": "Music videos", + "MusicVideos": "Music Videos", "NameInstallFailed": "{0} installation failed", "NameSeasonNumber": "Season {0}", "NameSeasonUnknown": "Season Unknown", From f75023593341bcc5adc42c8eb3644ba303ed67a0 Mon Sep 17 00:00:00 2001 From: Marcel <34819524+MarcelCoding@users.noreply.github.com> Date: Sun, 10 Oct 2021 15:15:43 +0000 Subject: [PATCH 198/278] Added healthcheck url environemnt variable --- Dockerfile | 5 +++-- Dockerfile.arm | 5 +++-- Dockerfile.arm64 | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index c4acf3c08dd..1a61790a838 100644 --- a/Dockerfile +++ b/Dockerfile @@ -77,6 +77,8 @@ RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release -- FROM app +ENV HEALTHCHECK_URL=http://localhost:8096/health + COPY --from=builder /jellyfin /jellyfin COPY --from=web-builder /dist /jellyfin/jellyfin-web @@ -88,5 +90,4 @@ ENTRYPOINT ["./jellyfin/jellyfin", \ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"] HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ - CMD curl http://localhost:$(grep -oP '(?<=HttpServerPortNumber>)[^<]+' /config/config/network.xml)/$(grep -oP '(?<=BaseUrl>)[^<]+' /config/config/network.xml)health \ - || exit 1 + CMD curl -L "${HEALTHCHECK_URL}" || exit 1 diff --git a/Dockerfile.arm b/Dockerfile.arm index f9d249cc174..35287dbbbc4 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -68,6 +68,8 @@ RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" FROM app +ENV HEALTHCHECK_URL=http://localhost:8096/health + COPY --from=builder /jellyfin /jellyfin COPY --from=web-builder /dist /jellyfin/jellyfin-web @@ -79,5 +81,4 @@ ENTRYPOINT ["./jellyfin/jellyfin", \ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"] HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ - CMD curl http://localhost:$(grep -oP '(?<=HttpServerPortNumber>)[^<]+' /config/config/network.xml)/$(grep -oP '(?<=BaseUrl>)[^<]+' /config/config/network.xml)health \ - || exit 1 + CMD curl -L "${HEALTHCHECK_URL}" || exit 1 diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index cf1a392eaa6..e120fed1cf4 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -59,6 +59,8 @@ RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" FROM app +ENV HEALTHCHECK_URL=http://localhost:8096/health + COPY --from=builder /jellyfin /jellyfin COPY --from=web-builder /dist /jellyfin/jellyfin-web @@ -70,5 +72,4 @@ ENTRYPOINT ["./jellyfin/jellyfin", \ "--ffmpeg", "/usr/bin/ffmpeg"] HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ - CMD curl http://localhost:$(grep -oP '(?<=HttpServerPortNumber>)[^<]+' /config/config/network.xml)/$(grep -oP '(?<=BaseUrl>)[^<]+' /config/config/network.xml)health \ - || exit 1 + CMD curl -L "${HEALTHCHECK_URL}" || exit 1 From b225c3fd3cb635fef5e75414b9a4bf1d183c92d6 Mon Sep 17 00:00:00 2001 From: Marcel <34819524+MarcelCoding@users.noreply.github.com> Date: Sun, 10 Oct 2021 17:26:06 +0200 Subject: [PATCH 199/278] Apply suggestions from code review Co-authored-by: Cody Robibero --- Dockerfile | 2 +- Dockerfile.arm | 2 +- Dockerfile.arm64 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1a61790a838..e133c081933 100644 --- a/Dockerfile +++ b/Dockerfile @@ -90,4 +90,4 @@ ENTRYPOINT ["./jellyfin/jellyfin", \ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"] HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ - CMD curl -L "${HEALTHCHECK_URL}" || exit 1 + CMD curl -Lk "${HEALTHCHECK_URL}" || exit 1 diff --git a/Dockerfile.arm b/Dockerfile.arm index 35287dbbbc4..a46fa331df4 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -81,4 +81,4 @@ ENTRYPOINT ["./jellyfin/jellyfin", \ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"] HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ - CMD curl -L "${HEALTHCHECK_URL}" || exit 1 + CMD curl -Lk "${HEALTHCHECK_URL}" || exit 1 diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index e120fed1cf4..1279c47f8eb 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -72,4 +72,4 @@ ENTRYPOINT ["./jellyfin/jellyfin", \ "--ffmpeg", "/usr/bin/ffmpeg"] HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ - CMD curl -L "${HEALTHCHECK_URL}" || exit 1 + CMD curl -Lk "${HEALTHCHECK_URL}" || exit 1 From c26e6d89b43cfd81c5735ae1a2ecd47cc43fda90 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sun, 10 Oct 2021 19:48:11 +0300 Subject: [PATCH 200/278] Move FfmpegException to MediaBrowser.Common --- .../FfmpegException.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 1 + MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) rename {MediaBrowser.MediaEncoding => MediaBrowser.Common}/FfmpegException.cs (97%) diff --git a/MediaBrowser.MediaEncoding/FfmpegException.cs b/MediaBrowser.Common/FfmpegException.cs similarity index 97% rename from MediaBrowser.MediaEncoding/FfmpegException.cs rename to MediaBrowser.Common/FfmpegException.cs index 1697fd33a1d..be420196d73 100644 --- a/MediaBrowser.MediaEncoding/FfmpegException.cs +++ b/MediaBrowser.Common/FfmpegException.cs @@ -1,6 +1,6 @@ using System; -namespace MediaBrowser.MediaEncoding +namespace MediaBrowser.Common { /// /// Represents errors that occur during interaction with FFmpeg. diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 06fe95ce82e..3dbbeda8a6a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -12,6 +12,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Jellyfin.Extensions.Json; +using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index bfc17904cd6..2b2de2ff6fe 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -11,6 +11,7 @@ using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; From 32f3d60a84a0e6e83ed0b00a1490e9fbd93a198d Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sun, 10 Oct 2021 19:48:48 +0300 Subject: [PATCH 201/278] Throw on FFmpeg non-zero exit code --- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 6 ++++++ Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 488856c4ea4..07d0b554337 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Data.Enums; +using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; @@ -607,6 +608,10 @@ namespace Jellyfin.Api.Helpers { StartThrottler(state, transcodingJob); } + else if (transcodingJob.ExitCode != 0) + { + throw new FfmpegException(string.Format(CultureInfo.InvariantCulture, "FFmpeg exited with code {0}", transcodingJob.ExitCode)); + } _logger.LogDebug("StartFfMpeg() finished successfully"); @@ -743,6 +748,7 @@ namespace Jellyfin.Api.Helpers private void OnFfMpegProcessExited(Process process, TranscodingJobDto job, StreamState state) { job.HasExited = true; + job.ExitCode = process.ExitCode; _logger.LogDebug("Disposing stream resources"); state.Dispose(); diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs index 291e571dcbc..fed837b856e 100644 --- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs +++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs @@ -106,6 +106,11 @@ namespace Jellyfin.Api.Models.PlaybackDtos /// public bool HasExited { get; set; } + /// + /// Gets or sets exit code. + /// + public int ExitCode { get; set; } + /// /// Gets or sets a value indicating whether is user paused. /// From 59e3beb5fe2bdcad106a5c02d416a53fb4589fcc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 12:00:48 +0000 Subject: [PATCH 202/278] Bump Diacritics from 2.1.20036.1 to 3.3.4 Bumps [Diacritics](https://github.com/thomasgalliker/Diacritics.NET) from 2.1.20036.1 to 3.3.4. - [Release notes](https://github.com/thomasgalliker/Diacritics.NET/releases) - [Commits](https://github.com/thomasgalliker/Diacritics.NET/commits) --- updated-dependencies: - dependency-name: Diacritics dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- MediaBrowser.Controller/MediaBrowser.Controller.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 786ebe3333e..007355acdc4 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -14,7 +14,7 @@ - + From 37a87a41baa5181b97795529d5e7f942270bbb48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 12:00:53 +0000 Subject: [PATCH 203/278] Bump Serilog.Settings.Configuration from 3.2.0 to 3.3.0 Bumps [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration) from 3.2.0 to 3.3.0. - [Release notes](https://github.com/serilog/serilog-settings-configuration/releases) - [Changelog](https://github.com/serilog/serilog-settings-configuration/blob/dev/CHANGES.md) - [Commits](https://github.com/serilog/serilog-settings-configuration/commits) --- updated-dependencies: - dependency-name: Serilog.Settings.Configuration dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 6603105fa33..be2318c5448 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -39,7 +39,7 @@ - + From be9ed29f63da3834e7d32e00909e5c5c3c6c8362 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Sun, 3 Oct 2021 21:28:21 +0200 Subject: [PATCH 204/278] Split EmbeddedImage extracting to new provider --- .../MediaInfo/EmbeddedImageProvider.cs | 123 ++++++++++++++++++ .../MediaInfo/VideoImageProvider.cs | 40 ++---- 2 files changed, 134 insertions(+), 29 deletions(-) create mode 100644 MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs new file mode 100644 index 00000000000..64a47611d82 --- /dev/null +++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs @@ -0,0 +1,123 @@ +#nullable enable +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.MediaInfo; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Providers.MediaInfo +{ + /// + /// Uses ffmpeg to extract embedded images. + /// + public class EmbeddedImageProvider : IDynamicImageProvider, IHasOrder + { + private readonly IMediaEncoder _mediaEncoder; + private readonly ILogger _logger; + + public EmbeddedImageProvider(IMediaEncoder mediaEncoder, ILogger logger) + { + _mediaEncoder = mediaEncoder; + _logger = logger; + } + + /// + public string Name => "Embedded Image Extractor"; + + /// + // Default to after internet image providers but before Screen Grabber + public int Order => 99; + + /// + public IEnumerable GetSupportedImages(BaseItem item) + { + return new[] { ImageType.Primary }; + } + + /// + public Task GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken) + { + var video = (Video)item; + + // No support for these + if (video.IsPlaceHolder || video.VideoType == VideoType.Dvd) + { + return Task.FromResult(new DynamicImageResponse { HasImage = false }); + } + + // Can't extract if we didn't find any video streams in the file + if (!video.DefaultVideoStreamIndex.HasValue) + { + _logger.LogInformation("Skipping image extraction due to missing DefaultVideoStreamIndex for {Path}.", video.Path ?? string.Empty); + return Task.FromResult(new DynamicImageResponse { HasImage = false }); + } + + return GetEmbeddedImage(video, cancellationToken); + } + + private async Task GetEmbeddedImage(Video item, CancellationToken cancellationToken) + { + MediaSourceInfo mediaSource = new MediaSourceInfo + { + VideoType = item.VideoType, + IsoType = item.IsoType, + Protocol = item.PathProtocol ?? MediaProtocol.File, + }; + + var imageStreams = + item.GetMediaStreams() + .Where(i => i.Type == MediaStreamType.EmbeddedImage) + .ToList(); + + string extractedImagePath; + + if (imageStreams.Count == 0) + { + // Can't extract if we don't have any EmbeddedImage streams + return new DynamicImageResponse { HasImage = false }; + } + else + { + var imageStream = imageStreams.Find(i => (i.Comment ?? string.Empty).Contains("front", StringComparison.OrdinalIgnoreCase)) + ?? imageStreams.Find(i => (i.Comment ?? string.Empty).Contains("cover", StringComparison.OrdinalIgnoreCase)) + ?? imageStreams[0]; + + extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, cancellationToken).ConfigureAwait(false); + } + + return new DynamicImageResponse + { + Format = ImageFormat.Jpg, + HasImage = true, + Path = extractedImagePath, + Protocol = MediaProtocol.File + }; + } + + /// + public bool Supports(BaseItem item) + { + if (item.IsShortcut) + { + return false; + } + + if (!item.IsFileProtocol) + { + return false; + } + + return item is Video video && !video.IsPlaceHolder && video.IsCompleteMedia; + } + } +} diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 8b96205c230..8f200995053 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -17,6 +17,9 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.MediaInfo { + /// + /// Uses ffmpeg to create still images from the main video. + /// public class VideoImageProvider : IDynamicImageProvider, IHasOrder { private readonly IMediaEncoder _mediaEncoder; @@ -71,36 +74,15 @@ namespace MediaBrowser.Providers.MediaInfo Protocol = item.PathProtocol ?? MediaProtocol.File, }; - var mediaStreams = - item.GetMediaStreams(); + // If we know the duration, grab it from 10% into the video. Otherwise just 10 seconds in. + // Always use 10 seconds for dvd because our duration could be out of whack + var imageOffset = item.VideoType != VideoType.Dvd && item.RunTimeTicks.HasValue && + item.RunTimeTicks.Value > 0 + ? TimeSpan.FromTicks(item.RunTimeTicks.Value / 10) + : TimeSpan.FromSeconds(10); - var imageStreams = - mediaStreams - .Where(i => i.Type == MediaStreamType.EmbeddedImage) - .ToList(); - - string extractedImagePath; - - if (imageStreams.Count == 0) - { - // If we know the duration, grab it from 10% into the video. Otherwise just 10 seconds in. - // Always use 10 seconds for dvd because our duration could be out of whack - var imageOffset = item.VideoType != VideoType.Dvd && item.RunTimeTicks.HasValue && - item.RunTimeTicks.Value > 0 - ? TimeSpan.FromTicks(item.RunTimeTicks.Value / 10) - : TimeSpan.FromSeconds(10); - - var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, videoStream, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); - } - else - { - var imageStream = imageStreams.Find(i => (i.Comment ?? string.Empty).Contains("front", StringComparison.OrdinalIgnoreCase)) - ?? imageStreams.Find(i => (i.Comment ?? string.Empty).Contains("cover", StringComparison.OrdinalIgnoreCase)) - ?? imageStreams[0]; - - extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, cancellationToken).ConfigureAwait(false); - } + var videoStream = item.GetMediaStreams().FirstOrDefault(i => i.Type == MediaStreamType.Video); + string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, videoStream, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); return new DynamicImageResponse { From 6ce8cce12c8d24cc0a49aac7a0d371e5ebf1b3a3 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Wed, 6 Oct 2021 01:13:08 +0200 Subject: [PATCH 205/278] Add handling for embedded background and logo --- .../Probing/ProbeResultNormalizer.cs | 15 ++- .../MediaInfo/EmbeddedImageProvider.cs | 91 ++++++++++++++++--- 2 files changed, 90 insertions(+), 16 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index fee3bf12ea3..5f653949595 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -735,14 +735,15 @@ namespace MediaBrowser.MediaEncoding.Probing else if (string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)) { // How to differentiate between video and embedded image? - // The only difference I've seen thus far is presence of codec tag, also embedded images have high (unusual) framerates - if (!string.IsNullOrWhiteSpace(stream.CodecTag)) + // check disposition, alternately: presence of codec tag, also embedded images have high (unusual) framerates + if ((streamInfo.Disposition != null && streamInfo.Disposition.GetValueOrDefault("attached_pic") == 1) || + string.IsNullOrWhiteSpace(stream.CodecTag)) { - stream.Type = MediaStreamType.Video; + stream.Type = MediaStreamType.EmbeddedImage; } else { - stream.Type = MediaStreamType.EmbeddedImage; + stream.Type = MediaStreamType.Video; } } else @@ -811,6 +812,12 @@ namespace MediaBrowser.MediaEncoding.Probing { stream.ColorPrimaries = streamInfo.ColorPrimaries; } + + // workaround for mkv attached_pics losing filename due to being classified as video based on codec + if (stream.Type == MediaStreamType.EmbeddedImage && streamInfo.Tags != null && string.IsNullOrEmpty(stream.Comment)) + { + stream.Comment = GetDictionaryValue(streamInfo.Tags, "filename"); + } } else { diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs index 64a47611d82..11fdfcdf95a 100644 --- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; @@ -22,6 +24,27 @@ namespace MediaBrowser.Providers.MediaInfo /// public class EmbeddedImageProvider : IDynamicImageProvider, IHasOrder { + private static readonly string[] _primaryImageFileNames = + { + "poster", + "folder", + "cover", + "default" + }; + + private static readonly string[] _backdropImageFileNames = + { + "backdrop", + "fanart", + "background", + "art" + }; + + private static readonly string[] _logoImageFileNames = + { + "logo", + }; + private readonly IMediaEncoder _mediaEncoder; private readonly ILogger _logger; @@ -41,7 +64,25 @@ namespace MediaBrowser.Providers.MediaInfo /// public IEnumerable GetSupportedImages(BaseItem item) { - return new[] { ImageType.Primary }; + if (item is Video) + { + if (item is Episode) + { + return new List + { + ImageType.Primary, + }; + } + + return new List + { + ImageType.Primary, + ImageType.Backdrop, + ImageType.Logo, + }; + } + + return ImmutableList.Empty; } /// @@ -62,10 +103,10 @@ namespace MediaBrowser.Providers.MediaInfo return Task.FromResult(new DynamicImageResponse { HasImage = false }); } - return GetEmbeddedImage(video, cancellationToken); + return GetEmbeddedImage(video, type, cancellationToken); } - private async Task GetEmbeddedImage(Video item, CancellationToken cancellationToken) + private async Task GetEmbeddedImage(Video item, ImageType type, CancellationToken cancellationToken) { MediaSourceInfo mediaSource = new MediaSourceInfo { @@ -74,27 +115,53 @@ namespace MediaBrowser.Providers.MediaInfo Protocol = item.PathProtocol ?? MediaProtocol.File, }; + string[] imageFileNames; + switch (type) + { + case ImageType.Backdrop: + imageFileNames = _backdropImageFileNames; + break; + case ImageType.Logo: + imageFileNames = _logoImageFileNames; + break; + case ImageType.Primary: + default: + imageFileNames = _primaryImageFileNames; + break; + } + var imageStreams = item.GetMediaStreams() .Where(i => i.Type == MediaStreamType.EmbeddedImage) .ToList(); - string extractedImagePath; - - if (imageStreams.Count == 0) + if (!imageStreams.Any()) { // Can't extract if we don't have any EmbeddedImage streams return new DynamicImageResponse { HasImage = false }; } - else - { - var imageStream = imageStreams.Find(i => (i.Comment ?? string.Empty).Contains("front", StringComparison.OrdinalIgnoreCase)) - ?? imageStreams.Find(i => (i.Comment ?? string.Empty).Contains("cover", StringComparison.OrdinalIgnoreCase)) - ?? imageStreams[0]; - extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, cancellationToken).ConfigureAwait(false); + // Extract first stream containing an element of imageFileNames + var imageStream = imageStreams + .Where(stream => !string.IsNullOrEmpty(stream.Comment)) + .First(stream => imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase))); + + // Primary type only: default to first image if none found by label + if (imageStream == null) + { + if (type == ImageType.Primary) + { + imageStream = imageStreams[0]; + } + else + { + // No streams matched, abort + return new DynamicImageResponse { HasImage = false }; + } } + string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, cancellationToken).ConfigureAwait(false); + return new DynamicImageResponse { Format = ImageFormat.Jpg, From 8d70cc2dde81aad0f484a2f0d0c5b90e6e1b97cd Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Thu, 7 Oct 2021 22:37:59 +0200 Subject: [PATCH 206/278] Add support for non-jpg image extractions --- .../MediaEncoding/IMediaEncoder.cs | 3 +- .../Encoder/MediaEncoder.cs | 21 +++---- .../Probing/ProbeResultNormalizer.cs | 18 ++---- .../MediaInfo/EmbeddedImageProvider.cs | 60 +++++++++++++------ 4 files changed, 62 insertions(+), 40 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index c5522bc3cf5..638588560d5 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -95,9 +95,10 @@ namespace MediaBrowser.Controller.MediaEncoding /// Media source information. /// Media stream information. /// Index of the stream to extract from. + /// The extension of the file to write. /// CancellationToken to use for operation. /// Location of video image. - Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken); + Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken); /// /// Extracts the video images on interval. diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 06fe95ce82e..30bc7125d7d 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -468,17 +468,17 @@ namespace MediaBrowser.MediaEncoding.Encoder Protocol = MediaProtocol.File }; - return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, cancellationToken); + return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, "jpg", cancellationToken); } public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) { - return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, cancellationToken); + return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, "jpg", cancellationToken); } - public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken) + public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken) { - return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, cancellationToken); + return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, outputExtension, cancellationToken); } private async Task ExtractImage( @@ -490,6 +490,7 @@ namespace MediaBrowser.MediaEncoding.Encoder bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, + string outputExtension, CancellationToken cancellationToken) { var inputArgument = GetInputArgument(inputFile, mediaSource); @@ -499,7 +500,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // The failure of HDR extraction usually occurs when using custom ffmpeg that does not contain the zscale filter. try { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, outputExtension, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -512,7 +513,7 @@ namespace MediaBrowser.MediaEncoding.Encoder try { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, outputExtension, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -525,7 +526,7 @@ namespace MediaBrowser.MediaEncoding.Encoder try { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, outputExtension, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -537,17 +538,17 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, outputExtension, cancellationToken).ConfigureAwait(false); } - private async Task ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, CancellationToken cancellationToken) + private async Task ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, string outputExtension, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(inputPath)) { throw new ArgumentNullException(nameof(inputPath)); } - var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg"); + var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + "." + outputExtension); Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath)); // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar. diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 5f653949595..775689095fe 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -582,7 +582,8 @@ namespace MediaBrowser.MediaEncoding.Probing /// MediaAttachments. private MediaAttachment GetMediaAttachment(MediaStreamInfo streamInfo) { - if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase) && + !(streamInfo.Disposition != null && streamInfo.Disposition.GetValueOrDefault("attached_pic") == 1)) { return null; } @@ -735,15 +736,14 @@ namespace MediaBrowser.MediaEncoding.Probing else if (string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)) { // How to differentiate between video and embedded image? - // check disposition, alternately: presence of codec tag, also embedded images have high (unusual) framerates - if ((streamInfo.Disposition != null && streamInfo.Disposition.GetValueOrDefault("attached_pic") == 1) || - string.IsNullOrWhiteSpace(stream.CodecTag)) + // The only difference I've seen thus far is presence of codec tag, also embedded images have high (unusual) framerates + if (!string.IsNullOrWhiteSpace(stream.CodecTag)) { - stream.Type = MediaStreamType.EmbeddedImage; + stream.Type = MediaStreamType.Video; } else { - stream.Type = MediaStreamType.Video; + stream.Type = MediaStreamType.EmbeddedImage; } } else @@ -812,12 +812,6 @@ namespace MediaBrowser.MediaEncoding.Probing { stream.ColorPrimaries = streamInfo.ColorPrimaries; } - - // workaround for mkv attached_pics losing filename due to being classified as video based on codec - if (stream.Type == MediaStreamType.EmbeddedImage && streamInfo.Tags != null && string.IsNullOrEmpty(stream.Comment)) - { - stream.Comment = GetDictionaryValue(streamInfo.Tags, "filename"); - } } else { diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs index 11fdfcdf95a..ad95cdb06fb 100644 --- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -15,6 +16,7 @@ using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.MediaInfo @@ -115,25 +117,49 @@ namespace MediaBrowser.Providers.MediaInfo Protocol = item.PathProtocol ?? MediaProtocol.File, }; - string[] imageFileNames; - switch (type) + string[] imageFileNames = type switch { - case ImageType.Backdrop: - imageFileNames = _backdropImageFileNames; - break; - case ImageType.Logo: - imageFileNames = _logoImageFileNames; - break; - case ImageType.Primary: - default: - imageFileNames = _primaryImageFileNames; - break; + ImageType.Primary => _primaryImageFileNames, + ImageType.Backdrop => _backdropImageFileNames, + ImageType.Logo => _logoImageFileNames, + _ => _primaryImageFileNames + }; + + // Try attachments first + var attachmentSources = item.GetMediaSources(false).SelectMany(source => source.MediaAttachments).ToList(); + var attachmentStream = attachmentSources + .Where(stream => !string.IsNullOrEmpty(stream.FileName)) + .First(stream => imageFileNames.Any(name => stream.FileName.Contains(name, StringComparison.OrdinalIgnoreCase))); + + if (attachmentStream != null) + { + var extension = (string.IsNullOrEmpty(attachmentStream.MimeType) ? + Path.GetExtension(attachmentStream.FileName) : + MimeTypes.ToExtension(attachmentStream.MimeType)) ?? "jpg"; + + string extractedAttachmentPath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken).ConfigureAwait(false); + + ImageFormat format = extension switch + { + "bmp" => ImageFormat.Bmp, + "gif" => ImageFormat.Gif, + "jpg" => ImageFormat.Jpg, + "png" => ImageFormat.Png, + "webp" => ImageFormat.Webp, + _ => ImageFormat.Jpg + }; + + return new DynamicImageResponse + { + Format = format, + HasImage = true, + Path = extractedAttachmentPath, + Protocol = MediaProtocol.File + }; } - var imageStreams = - item.GetMediaStreams() - .Where(i => i.Type == MediaStreamType.EmbeddedImage) - .ToList(); + // Fall back to EmbeddedImage streams + var imageStreams = item.GetMediaStreams().FindAll(i => i.Type == MediaStreamType.EmbeddedImage); if (!imageStreams.Any()) { @@ -160,7 +186,7 @@ namespace MediaBrowser.Providers.MediaInfo } } - string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, cancellationToken).ConfigureAwait(false); + string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, "jpg", cancellationToken).ConfigureAwait(false); return new DynamicImageResponse { From e3eee10d05e9ecc7e3fac1f8fdad92329d38a4db Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Mon, 11 Oct 2021 12:34:18 +0200 Subject: [PATCH 207/278] Add image provider tests and clean up --- .../MediaEncoding/IMediaEncoder.cs | 2 +- .../Encoder/MediaEncoder.cs | 6 +- .../MediaInfo/EmbeddedImageProvider.cs | 41 ++-- .../MediaInfo/VideoImageProvider.cs | 9 +- .../MediaInfo/EmbeddedImageProviderTests.cs | 211 ++++++++++++++++++ .../MediaInfo/VideoImageProviderTests.cs | 168 ++++++++++++++ 6 files changed, 408 insertions(+), 29 deletions(-) create mode 100644 tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 638588560d5..e6511ca8d7a 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -95,7 +95,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Media source information. /// Media stream information. /// Index of the stream to extract from. - /// The extension of the file to write. + /// The extension of the file to write, including the '.'. /// CancellationToken to use for operation. /// Location of video image. Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 30bc7125d7d..dac2c6a26a3 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -468,12 +468,12 @@ namespace MediaBrowser.MediaEncoding.Encoder Protocol = MediaProtocol.File }; - return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, "jpg", cancellationToken); + return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, ".jpg", cancellationToken); } public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) { - return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, "jpg", cancellationToken); + return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, ".jpg", cancellationToken); } public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken) @@ -548,7 +548,7 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new ArgumentNullException(nameof(inputPath)); } - var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + "." + outputExtension); + var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + outputExtension); Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath)); // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar. diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs index ad95cdb06fb..df87f2d49cc 100644 --- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs @@ -1,9 +1,7 @@ -#nullable enable #pragma warning disable CS1591 using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.IO; using System.Linq; using System.Threading; @@ -17,7 +15,6 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; -using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.MediaInfo { @@ -48,12 +45,10 @@ namespace MediaBrowser.Providers.MediaInfo }; private readonly IMediaEncoder _mediaEncoder; - private readonly ILogger _logger; - public EmbeddedImageProvider(IMediaEncoder mediaEncoder, ILogger logger) + public EmbeddedImageProvider(IMediaEncoder mediaEncoder) { _mediaEncoder = mediaEncoder; - _logger = logger; } /// @@ -84,7 +79,7 @@ namespace MediaBrowser.Providers.MediaInfo }; } - return ImmutableList.Empty; + return new List(); } /// @@ -98,13 +93,6 @@ namespace MediaBrowser.Providers.MediaInfo return Task.FromResult(new DynamicImageResponse { HasImage = false }); } - // Can't extract if we didn't find any video streams in the file - if (!video.DefaultVideoStreamIndex.HasValue) - { - _logger.LogInformation("Skipping image extraction due to missing DefaultVideoStreamIndex for {Path}.", video.Path ?? string.Empty); - return Task.FromResult(new DynamicImageResponse { HasImage = false }); - } - return GetEmbeddedImage(video, type, cancellationToken); } @@ -128,24 +116,29 @@ namespace MediaBrowser.Providers.MediaInfo // Try attachments first var attachmentSources = item.GetMediaSources(false).SelectMany(source => source.MediaAttachments).ToList(); var attachmentStream = attachmentSources - .Where(stream => !string.IsNullOrEmpty(stream.FileName)) - .First(stream => imageFileNames.Any(name => stream.FileName.Contains(name, StringComparison.OrdinalIgnoreCase))); + .Where(attachment => !string.IsNullOrEmpty(attachment.FileName)) + .FirstOrDefault(attachment => imageFileNames.Any(name => attachment.FileName.Contains(name, StringComparison.OrdinalIgnoreCase))); if (attachmentStream != null) { - var extension = (string.IsNullOrEmpty(attachmentStream.MimeType) ? + var extension = string.IsNullOrEmpty(attachmentStream.MimeType) ? Path.GetExtension(attachmentStream.FileName) : - MimeTypes.ToExtension(attachmentStream.MimeType)) ?? "jpg"; + MimeTypes.ToExtension(attachmentStream.MimeType); + + if (string.IsNullOrEmpty(extension)) + { + extension = ".jpg"; + } string extractedAttachmentPath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken).ConfigureAwait(false); ImageFormat format = extension switch { - "bmp" => ImageFormat.Bmp, - "gif" => ImageFormat.Gif, - "jpg" => ImageFormat.Jpg, - "png" => ImageFormat.Png, - "webp" => ImageFormat.Webp, + ".bmp" => ImageFormat.Bmp, + ".gif" => ImageFormat.Gif, + ".jpg" => ImageFormat.Jpg, + ".png" => ImageFormat.Png, + ".webp" => ImageFormat.Webp, _ => ImageFormat.Jpg }; @@ -170,7 +163,7 @@ namespace MediaBrowser.Providers.MediaInfo // Extract first stream containing an element of imageFileNames var imageStream = imageStreams .Where(stream => !string.IsNullOrEmpty(stream.Comment)) - .First(stream => imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase))); + .FirstOrDefault(stream => imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase))); // Primary type only: default to first image if none found by label if (imageStream == null) diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 8f200995053..60739f15640 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -81,7 +81,14 @@ namespace MediaBrowser.Providers.MediaInfo ? TimeSpan.FromTicks(item.RunTimeTicks.Value / 10) : TimeSpan.FromSeconds(10); - var videoStream = item.GetMediaStreams().FirstOrDefault(i => i.Type == MediaStreamType.Video); + var videoStream = item.GetDefaultVideoStream() ?? item.GetMediaStreams().FirstOrDefault(i => i.Type == MediaStreamType.Video); + + if (videoStream == null) + { + _logger.LogInformation("Skipping image extraction: no video stream found for {Path}.", item.Path ?? string.Empty); + return new DynamicImageResponse { HasImage = false }; + } + string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, videoStream, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); return new DynamicImageResponse diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs new file mode 100644 index 00000000000..fcea1532af5 --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs @@ -0,0 +1,211 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Providers.MediaInfo; +using Moq; +using Xunit; + +namespace Jellyfin.Providers.Tests.MediaInfo +{ + public class EmbeddedImageProviderTests + { + public static TheoryData GetSupportedImages_Empty_TestData => + new () + { + new AudioBook(), + new BoxSet(), + new Series(), + new Season(), + }; + + public static TheoryData> GetSupportedImages_Populated_TestData => + new TheoryData> + { + { new Episode(), new List { ImageType.Primary } }, + { new Movie(), new List { ImageType.Logo, ImageType.Backdrop, ImageType.Primary } }, + }; + + private EmbeddedImageProvider GetEmbeddedImageProvider(IMediaEncoder? mediaEncoder) + { + return new EmbeddedImageProvider(mediaEncoder); + } + + [Theory] + [MemberData(nameof(GetSupportedImages_Empty_TestData))] + public void GetSupportedImages_Empty(BaseItem item) + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + Assert.False(embeddedImageProvider.GetSupportedImages(item).Any()); + } + + [Theory] + [MemberData(nameof(GetSupportedImages_Populated_TestData))] + public void GetSupportedImages_Populated(BaseItem item, IEnumerable expected) + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + var actual = embeddedImageProvider.GetSupportedImages(item); + Assert.Equal(expected.OrderBy(i => i.ToString()), actual.OrderBy(i => i.ToString())); + } + + [Fact] + public async void GetImage_Empty_NoStreams() + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List()); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List()); + + var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Empty_NoLabeledAttachments() + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + + var input = new Mock(); + // add an attachment without a filename - has a list to look through but finds nothing + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List { new () { MediaAttachments = new List { new () } } }); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List()); + + var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Empty_NoEmbeddedLabeledBackdrop() + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List()); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List { new () { Type = MediaStreamType.EmbeddedImage } }); + + var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Attached() + { + // first tests file extension detection, second uses mimetype, third defaults to jpg + MediaAttachment sampleAttachment1 = new () { FileName = "clearlogo.png", Index = 1 }; + MediaAttachment sampleAttachment2 = new () { FileName = "backdrop", MimeType = "image/bmp", Index = 2 }; + MediaAttachment sampleAttachment3 = new () { FileName = "poster", Index = 3 }; + string targetPath1 = "path1.png"; + string targetPath2 = "path2.bmp"; + string targetPath3 = "path2.jpg"; + + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), 1, ".png", CancellationToken.None)) + .Returns(Task.FromResult(targetPath1)); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), 2, ".bmp", CancellationToken.None)) + .Returns(Task.FromResult(targetPath2)); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), 3, ".jpg", CancellationToken.None)) + .Returns(Task.FromResult(targetPath3)); + var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List { new () { MediaAttachments = new List { sampleAttachment1, sampleAttachment2, sampleAttachment3 } } }); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List()); + + var actualLogo = await embeddedImageProvider.GetImage(input.Object, ImageType.Logo, CancellationToken.None); + Assert.NotNull(actualLogo); + Assert.True(actualLogo.HasImage); + Assert.Equal(targetPath1, actualLogo.Path); + Assert.Equal(ImageFormat.Png, actualLogo.Format); + + var actualBackdrop = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); + Assert.NotNull(actualBackdrop); + Assert.True(actualBackdrop.HasImage); + Assert.Equal(targetPath2, actualBackdrop.Path); + Assert.Equal(ImageFormat.Bmp, actualBackdrop.Format); + + var actualPrimary = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actualPrimary); + Assert.True(actualPrimary.HasImage); + Assert.Equal(targetPath3, actualPrimary.Path); + Assert.Equal(ImageFormat.Jpg, actualPrimary.Format); + } + + [Fact] + public async void GetImage_EmbeddedDefault() + { + MediaStream sampleStream = new () { Type = MediaStreamType.EmbeddedImage, Index = 1 }; + string targetPath = "path"; + + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream, 1, "jpg", CancellationToken.None)) + .Returns(Task.FromResult(targetPath)); + var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List()); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List() { sampleStream }); + + var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.True(actual.HasImage); + Assert.Equal(targetPath, actual.Path); + Assert.Equal(ImageFormat.Jpg, actual.Format); + } + + [Fact] + public async void GetImage_EmbeddedSelection() + { + // primary is second stream to ensure it's not defaulting, backdrop is first + MediaStream sampleStream1 = new () { Type = MediaStreamType.EmbeddedImage, Index = 1, Comment = "backdrop" }; + MediaStream sampleStream2 = new () { Type = MediaStreamType.EmbeddedImage, Index = 2, Comment = "cover" }; + string targetPath1 = "path1.jpg"; + string targetPath2 = "path2.jpg"; + + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream1, 1, "jpg", CancellationToken.None)) + .Returns(Task.FromResult(targetPath1)); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream2, 2, "jpg", CancellationToken.None)) + .Returns(Task.FromResult(targetPath2)); + var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List()); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List { sampleStream1, sampleStream2 }); + + var actualPrimary = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actualPrimary); + Assert.True(actualPrimary.HasImage); + Assert.Equal(targetPath2, actualPrimary.Path); + Assert.Equal(ImageFormat.Jpg, actualPrimary.Format); + + var actualBackdrop = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); + Assert.NotNull(actualBackdrop); + Assert.True(actualBackdrop.HasImage); + Assert.Equal(targetPath1, actualBackdrop.Path); + Assert.Equal(ImageFormat.Jpg, actualBackdrop.Format); + } + } +} diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs new file mode 100644 index 00000000000..9a5cd79bbff --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Providers.MediaInfo; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Jellyfin.Providers.Tests.MediaInfo +{ + public class VideoImageProviderTests + { + private VideoImageProvider GetVideoImageProvider(IMediaEncoder? mediaEncoder) + { + // strict to ensure this isn't accidentally used where a prepared mock is intended + mediaEncoder ??= new Mock(MockBehavior.Strict).Object; + return new VideoImageProvider(mediaEncoder, new NullLogger()); + } + + [Fact] + public async void GetImage_Empty_IsPlaceholder() + { + var videoImageProvider = GetVideoImageProvider(null); + + var input = new Mock(); + input.Object.IsPlaceHolder = true; + + var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Empty_NoDefaultVideoStream() + { + var videoImageProvider = GetVideoImageProvider(null); + + var input = new Mock(); + + var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Empty_DefaultSet_NoVideoStream() + { + var videoImageProvider = GetVideoImageProvider(null); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List()); + // set a default index but don't put anything there (invalid input, but provider shouldn't break) + input.Object.DefaultVideoStreamIndex = 1; + + var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Extract_DefaultStream() + { + MediaStream firstStream = new () { Type = MediaStreamType.Video, Index = 0 }; + MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 1 }; + string targetPath = "path.jpg"; + + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), firstStream, It.IsAny(), It.IsAny(), CancellationToken.None)) + .Returns(Task.FromResult("wrong stream called!")); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), targetStream, It.IsAny(), It.IsAny(), CancellationToken.None)) + .Returns(Task.FromResult(targetPath)); + var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetDefaultVideoStream()) + .Returns(targetStream); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List() { firstStream, targetStream }); + input.Object.DefaultVideoStreamIndex = 1; + + var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.True(actual.HasImage); + Assert.Equal(targetPath, actual.Path); + Assert.Equal(ImageFormat.Jpg, actual.Format); + } + + [Fact] + public async void GetImage_Extract_FallbackToFirstVideoStream() + { + MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; + string targetPath = "path.jpg"; + + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), targetStream, It.IsAny(), It.IsAny(), CancellationToken.None)) + .Returns(Task.FromResult(targetPath)); + var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List() { targetStream }); + // default must be set, ensure a stream is still found if not pointed at a video + input.Object.DefaultVideoStreamIndex = 5; + + var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.True(actual.HasImage); + Assert.Equal(targetPath, actual.Path); + Assert.Equal(ImageFormat.Jpg, actual.Format); + } + + [Fact] + public async void GetImage_Time_Default() + { + MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; + + TimeSpan? actualTimeSpan = null; + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), CancellationToken.None)) + .Callback((_, _, _, _, _, timeSpan, _) => actualTimeSpan = timeSpan) + .Returns(Task.FromResult("path")); + var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List() { targetStream }); + // default must be set + input.Object.DefaultVideoStreamIndex = 0; + + // not testing return, just verifying what gets requested for time span + await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + + Assert.Equal(TimeSpan.FromSeconds(10), actualTimeSpan); + } + + [Fact] + public async void GetImage_Time_Calculated() + { + MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; + + TimeSpan? actualTimeSpan = null; + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), CancellationToken.None)) + .Callback((_, _, _, _, _, timeSpan, _) => actualTimeSpan = timeSpan) + .Returns(Task.FromResult("path")); + var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List() { targetStream }); + // default must be set + input.Object.DefaultVideoStreamIndex = 0; + input.Object.RunTimeTicks = 5000; + + // not testing return, just verifying what gets requested for time span + await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + + Assert.Equal(TimeSpan.FromTicks(500), actualTimeSpan); + } + } +} From e3edd7ae2c7d8bc564537bca7e7b1cbf91c2713e Mon Sep 17 00:00:00 2001 From: rimasx Date: Mon, 11 Oct 2021 15:39:56 -0400 Subject: [PATCH 208/278] Added translation using Weblate (Estonian) --- Emby.Server.Implementations/Localization/Core/et.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 Emby.Server.Implementations/Localization/Core/et.json diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/et.json @@ -0,0 +1 @@ +{} From 2b10251b32ad00290f6be00060ec6ccf47574b5d Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Tue, 12 Oct 2021 18:31:58 -0600 Subject: [PATCH 209/278] Update to dotnet6.rc2 --- Emby.Dlna/Emby.Dlna.csproj | 2 +- .../Emby.Server.Implementations.csproj | 10 +++++----- Jellyfin.Api/Jellyfin.Api.csproj | 4 ++-- Jellyfin.Data/Jellyfin.Data.csproj | 2 +- .../Jellyfin.Server.Implementations.csproj | 8 ++++---- Jellyfin.Server/Jellyfin.Server.csproj | 8 ++++---- MediaBrowser.Common/MediaBrowser.Common.csproj | 4 ++-- MediaBrowser.Controller/MediaBrowser.Controller.csproj | 6 +++--- .../MediaBrowser.MediaEncoding.csproj | 4 ++-- MediaBrowser.Model/MediaBrowser.Model.csproj | 4 ++-- MediaBrowser.Providers/MediaBrowser.Providers.csproj | 6 +++--- deployment/Dockerfile.centos.amd64 | 2 +- deployment/Dockerfile.fedora.amd64 | 2 +- deployment/Dockerfile.ubuntu.amd64 | 2 +- deployment/Dockerfile.ubuntu.arm64 | 2 +- deployment/Dockerfile.ubuntu.armhf | 2 +- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 4 ++-- .../Jellyfin.Server.Integration.Tests.csproj | 4 ++-- .../Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 4 ++-- 19 files changed, 40 insertions(+), 40 deletions(-) diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index e4f30d4e0ac..c8332e44e44 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -72,7 +72,7 @@ - + diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index dafcded0894..9372cba9f20 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -25,11 +25,11 @@ - - - - - + + + + + diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index cdc69618a99..8a559704c06 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index f1bfaa63e0e..2de53e7c838 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -35,7 +35,7 @@ - + diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index d9e6d794b3c..e26cf093b9c 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -19,13 +19,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index be2318c5448..8983eb50fdd 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -31,10 +31,10 @@ - - - - + + + + diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index c87d58a147e..9c8ce4ac57b 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -19,8 +19,8 @@ - - + + diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 007355acdc4..d378808658c 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -15,10 +15,10 @@ - - + + - + diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 22bba23663e..a6caca8db7c 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -24,8 +24,8 @@ - - + + diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index e6a5b1711ca..16bc4adf8ab 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -30,9 +30,9 @@ - + - + diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 71a3554fd69..15badfad7e1 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -16,9 +16,9 @@ - - - + + + diff --git a/deployment/Dockerfile.centos.amd64 b/deployment/Dockerfile.centos.amd64 index 178f94f7199..78f051e4fb4 100644 --- a/deployment/Dockerfile.centos.amd64 +++ b/deployment/Dockerfile.centos.amd64 @@ -13,7 +13,7 @@ RUN yum update -yq \ && yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget # Install DotNET SDK -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5fcb98bb-21e1-47a5-bb8e-bb25f41a3e52/04811d5d05b7e694f040d2a13c1aae4c/dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/20283373-1d83-4879-8278-0afb7fd4035e/56f204f174743b29a656499ad0fc93c3/dotnet-sdk-6.0.100-rc.2.21505.57-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.fedora.amd64 b/deployment/Dockerfile.fedora.amd64 index f0f2977a406..14eeb6eed8e 100644 --- a/deployment/Dockerfile.fedora.amd64 +++ b/deployment/Dockerfile.fedora.amd64 @@ -12,7 +12,7 @@ RUN dnf update -yq \ && dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget # Install DotNET SDK -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5fcb98bb-21e1-47a5-bb8e-bb25f41a3e52/04811d5d05b7e694f040d2a13c1aae4c/dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/20283373-1d83-4879-8278-0afb7fd4035e/56f204f174743b29a656499ad0fc93c3/dotnet-sdk-6.0.100-rc.2.21505.57-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64 index fe1b4981bcf..8733be89cf7 100644 --- a/deployment/Dockerfile.ubuntu.amd64 +++ b/deployment/Dockerfile.ubuntu.amd64 @@ -17,7 +17,7 @@ RUN apt-get update -yqq \ libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 # Install dotnet repository -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5fcb98bb-21e1-47a5-bb8e-bb25f41a3e52/04811d5d05b7e694f040d2a13c1aae4c/dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/20283373-1d83-4879-8278-0afb7fd4035e/56f204f174743b29a656499ad0fc93c3/dotnet-sdk-6.0.100-rc.2.21505.57-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64 index d984f5d8987..6ae0d53ccbd 100644 --- a/deployment/Dockerfile.ubuntu.arm64 +++ b/deployment/Dockerfile.ubuntu.arm64 @@ -16,7 +16,7 @@ RUN apt-get update -yqq \ mmv build-essential lsb-release # Install dotnet repository -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5fcb98bb-21e1-47a5-bb8e-bb25f41a3e52/04811d5d05b7e694f040d2a13c1aae4c/dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/20283373-1d83-4879-8278-0afb7fd4035e/56f204f174743b29a656499ad0fc93c3/dotnet-sdk-6.0.100-rc.2.21505.57-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf index c013e6797ea..154388148c3 100644 --- a/deployment/Dockerfile.ubuntu.armhf +++ b/deployment/Dockerfile.ubuntu.armhf @@ -16,7 +16,7 @@ RUN apt-get update -yqq \ mmv build-essential lsb-release # Install dotnet repository -RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5fcb98bb-21e1-47a5-bb8e-bb25f41a3e52/04811d5d05b7e694f040d2a13c1aae4c/dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget -q https://download.visualstudio.microsoft.com/download/pr/20283373-1d83-4879-8278-0afb7fd4035e/56f204f174743b29a656499ad0fc93c3/dotnet-sdk-6.0.100-rc.2.21505.57-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 8b581857fea..922b3d94f76 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 38687ae61af..9d7b447edd5 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index db24df240c3..67ae0e0802a 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -10,8 +10,8 @@ - - + + From e88e1fa8e9aa49323f4b55fd8cc6185cd9fb9059 Mon Sep 17 00:00:00 2001 From: JULIAN AUGUSTO RUIZ MARTINEZ Date: Wed, 13 Oct 2021 19:30:04 +0000 Subject: [PATCH 210/278] Translated using Weblate (Pirate) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pr/ --- Emby.Server.Implementations/Localization/Core/pr.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/pr.json b/Emby.Server.Implementations/Localization/Core/pr.json index 0967ef424bc..e3a3bfaf1c9 100644 --- a/Emby.Server.Implementations/Localization/Core/pr.json +++ b/Emby.Server.Implementations/Localization/Core/pr.json @@ -1 +1,5 @@ -{} +{ + "Books": "Libros", + "AuthenticationSucceededWithUserName": "{0} autentificado correctamente", + "Artists": "Artistas" +} From a720b12ec33f3b3d9dc1121d71f929a739a70012 Mon Sep 17 00:00:00 2001 From: lyaschuchenko Date: Tue, 12 Oct 2021 14:24:18 +0000 Subject: [PATCH 211/278] Translated using Weblate (Ukrainian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/uk/ --- Emby.Server.Implementations/Localization/Core/uk.json | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/uk.json b/Emby.Server.Implementations/Localization/Core/uk.json index 5a2069df564..e99ed6f0b90 100644 --- a/Emby.Server.Implementations/Localization/Core/uk.json +++ b/Emby.Server.Implementations/Localization/Core/uk.json @@ -1,5 +1,5 @@ { - "MusicVideos": "Музичні відеокліпи", + "MusicVideos": "Відеокліпи", "Music": "Музика", "Movies": "Фільми", "MessageApplicationUpdatedTo": "Jellyfin Server оновлено до версії {0}", @@ -16,7 +16,7 @@ "HeaderFavoriteArtists": "Улюблені виконавці", "HeaderFavoriteAlbums": "Улюблені альбоми", "HeaderContinueWatching": "Продовжити перегляд", - "HeaderAlbumArtists": "Виконавці альбому", + "HeaderAlbumArtists": "Виконавці альбомів", "Genres": "Жанри", "Folders": "Каталоги", "Favorites": "Улюблені", @@ -38,7 +38,7 @@ "NotificationOptionPluginInstalled": "Плагін встановлено", "NotificationOptionPluginError": "Помилка плагіна", "NotificationOptionNewLibraryContent": "Додано новий контент", - "HomeVideos": "Домашнє відео", + "HomeVideos": "Мої відео", "FailedLoginAttemptWithUserName": "Невдала спроба входу від {0}", "LabelRunningTimeValue": "Тривалість: {0}", "TaskDownloadMissingSubtitlesDescription": "Шукає в Інтернеті відсутні субтитри на основі конфігурації метаданих.", @@ -117,5 +117,7 @@ "TaskCleanActivityLogDescription": "Видаляє старші за встановлений термін записи з журналу активності.", "TaskCleanActivityLog": "Очистити журнал активності", "Undefined": "Не визначено", - "Default": "За замовчуванням" + "Default": "За замовчуванням", + "TaskOptimizeDatabase": "Оптимізувати базу даних", + "TaskOptimizeDatabaseDescription": "Стиснення бази даних та збільшення вільного простору. Виконання цього завдання після сканування бібліотеки або внесення інших змін, які передбачають модифікацію бази даних, може покращити продуктивність." } From 6cc4be85fc6362b2f0552b315ff73dbad686eccd Mon Sep 17 00:00:00 2001 From: rimasx Date: Wed, 13 Oct 2021 13:54:46 +0000 Subject: [PATCH 212/278] Translated using Weblate (Estonian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/et/ --- .../Localization/Core/et.json | 119 +++++++++++++++++- 1 file changed, 118 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json index 0967ef424bc..3e57d96ca29 100644 --- a/Emby.Server.Implementations/Localization/Core/et.json +++ b/Emby.Server.Implementations/Localization/Core/et.json @@ -1 +1,118 @@ -{} +{ + "TaskCleanActivityLogDescription": "Kustutab määratud ajast vanemad tegevuslogi kirjed.", + "UserDownloadingItemWithValues": "{0} laeb alla {1}", + "HeaderRecordingGroups": "Salvestusrühmad", + "TaskOptimizeDatabaseDescription": "Tihendab ja puhastab andmebaasi. Selle toimingu tegemine pärast meediakogu andmebaasiga seotud muudatuste skannimist võib jõudlust parandada.", + "TaskOptimizeDatabase": "Optimeeri andmebaasi", + "TaskDownloadMissingSubtitlesDescription": "Otsib veebist puuduvaid subtiitreid vastavalt määratud metaandmete seadetele.", + "TaskDownloadMissingSubtitles": "Laadi alla puuduvad subtiitrid", + "TaskRefreshChannelsDescription": "Värskendab veebikanalite teavet.", + "TaskRefreshChannels": "Värskenda kanaleid", + "TaskCleanTranscodeDescription": "Kustutab üle ühe päeva vanused transkodeerimisfailid.", + "TaskCleanTranscode": "Puhasta transkoodimise kataloog", + "TaskUpdatePluginsDescription": "Laadib alla ja paigaldab nende pluginate uuendused, mis on seadistatud automaatselt uuenduma.", + "TaskUpdatePlugins": "Uuenda pluginaid", + "TaskRefreshPeopleDescription": "Värskendab meediakogus näitlejate ja režissööride metaandmeid.", + "TaskRefreshPeople": "Värskenda inimesi", + "TaskCleanLogsDescription": "Kustutab logifailid, mis on vanemad kui {0} päeva.", + "TaskCleanLogs": "Puhasta logikataloog", + "TaskRefreshLibraryDescription": "Otsib meedikogust uusi faile ja värskendab metaandmeid.", + "Collections": "Kollektsioonid", + "TaskRefreshLibrary": "Skaneeri meediakogu", + "TaskRefreshChapterImagesDescription": "Loob peatükkidega videote jaoks pisipildid.", + "TaskRefreshChapterImages": "Eralda peatükipildid", + "TaskCleanCacheDescription": "Kustutab vahemälufailid, mida süsteem enam ei vaja.", + "TaskCleanCache": "Puhasta vahemälu kataloog", + "TaskCleanActivityLog": "Puhasta tegevuslogi", + "TasksChannelsCategory": "Veebikanalid", + "TasksApplicationCategory": "Rakendus", + "TasksLibraryCategory": "Meediakogu", + "TasksMaintenanceCategory": "Hooldus", + "VersionNumber": "Versioon {0}", + "ValueSpecialEpisodeName": "Eriepisood - {0}", + "ValueHasBeenAddedToLibrary": "{0} lisati meediakogusse", + "UserStartedPlayingItemWithValues": "{0} taasesitab {1} serveris {2}", + "UserPasswordChangedWithName": "Kasutaja {0} parooli on muudetud", + "UserLockedOutWithName": "Kasutaja {0} on lukustatud", + "UserDeletedWithName": "Kasutaja {0} on kustutatud", + "UserCreatedWithName": "Kasutaja {0} on loodud", + "ScheduledTaskStartedWithName": "{0} käivitati", + "ProviderValue": "Allikas: {0}", + "StartupEmbyServerIsLoading": "Jellyfin server laadib. Proovi varsti uuesti.", + "User": "Kasutaja", + "Undefined": "Määratlemata", + "TvShows": "Seriaalid", + "System": "Süsteem", + "Sync": "Sünkrooni", + "Songs": "Laulud", + "Shows": "Seriaalid", + "ServerNameNeedsToBeRestarted": "{0} tuleb taaskäivitada", + "ScheduledTaskFailedWithName": "{0} nurjus", + "PluginUpdatedWithName": "{0} uuendati", + "PluginUninstalledWithName": "{0} eemaldati", + "PluginInstalledWithName": "{0} paigaldati", + "Plugin": "Plugin", + "Playlists": "Pleilistid", + "Photos": "Fotod", + "NotificationOptionVideoPlaybackStopped": "Video taasesitus on peatatud", + "NotificationOptionVideoPlayback": "Video taasesitus algas", + "NotificationOptionUserLockedOut": "Kasutaja on lukustatud", + "NotificationOptionTaskFailed": "Ajastatud ülesanne nurjus", + "NotificationOptionServerRestartRequired": "Vajalik on serveri taaskäivitamine", + "NotificationOptionPluginUpdateInstalled": "Paigaldati plugina uuendus", + "NotificationOptionPluginUninstalled": "Plugin eemaldati", + "NotificationOptionPluginInstalled": "Plugin paigaldati", + "NotificationOptionPluginError": "Plugina tõrge", + "NotificationOptionNewLibraryContent": "Lisati uut sisu", + "NotificationOptionInstallationFailed": "Paigaldamine nurjus", + "NotificationOptionCameraImageUploaded": "Kaamera pilt on üles laaditud", + "NotificationOptionAudioPlaybackStopped": "Heli taasesitus peatati", + "NotificationOptionAudioPlayback": "Heli taasesitus algas", + "NotificationOptionApplicationUpdateInstalled": "Rakenduse uuendus paigaldati", + "NotificationOptionApplicationUpdateAvailable": "Rakenduse uuendus on saadaval", + "NewVersionIsAvailable": "Jellyfin serveri uus versioon on allalaadimiseks saadaval.", + "NameSeasonUnknown": "Tundmatu hooaeg", + "NameSeasonNumber": "Hooaeg {0}", + "NameInstallFailed": "{0} paigaldamine nurjus", + "MusicVideos": "Muusikavideod", + "Music": "Muusika", + "Movies": "Filmid", + "MixedContent": "Segatud sisu", + "MessageServerConfigurationUpdated": "Serveri seadistust uuendati", + "MessageNamedServerConfigurationUpdatedWithValue": "Serveri seadistusosa {0} uuendati", + "MessageApplicationUpdatedTo": "Jellyfin server uuendati versioonile {0}", + "MessageApplicationUpdated": "Jellyfin server uuendati", + "Latest": "Uusimad", + "LabelRunningTimeValue": "Kestus: {0}", + "LabelIpAddressValue": "IP aadress: {0}", + "ItemRemovedWithName": "{0} eemaldati meediakogust", + "ItemAddedWithName": "{0} lisati meediakogusse", + "Inherit": "Päri", + "HomeVideos": "Koduvideod", + "HeaderNextUp": "Järgmisena", + "HeaderLiveTV": "Otse TV", + "HeaderFavoriteSongs": "Lemmiklood", + "HeaderFavoriteShows": "Lemmikseriaalid", + "HeaderFavoriteEpisodes": "Lemmikepisoodid", + "HeaderFavoriteArtists": "Lemmikesitajad", + "HeaderFavoriteAlbums": "Lemmikalbumid", + "HeaderContinueWatching": "Jätka vaatamist", + "HeaderAlbumArtists": "Albumi esitaja", + "Genres": "Žanrid", + "Forced": "Sunnitud", + "Folders": "Kaustad", + "Favorites": "Lemmikud", + "FailedLoginAttemptWithUserName": "Ebaõnnestunud sisselogimiskatse kasutajalt {0}", + "DeviceOnlineWithName": "{0} on ühendatud", + "DeviceOfflineWithName": "{0} katkestas ühenduse", + "Default": "Vaikimisi", + "ChapterNameValue": "Peatükk {0}", + "Channels": "Kanalid", + "CameraImageUploadedFrom": "Uus kaamera pilt laaditi üles allikalt {0}", + "Books": "Raamatud", + "AuthenticationSucceededWithUserName": "{0} autentimine õnnestus", + "Artists": "Esitajad", + "Application": "Rakendus", + "AppDeviceValues": "Rakendus: {0}, seade: {1}", + "Albums": "Albumid" +} From 0b937c32d5a0a5c9078a929eebb624bc84471f29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Oct 2021 12:00:59 +0000 Subject: [PATCH 213/278] Bump Swashbuckle.AspNetCore from 6.2.2 to 6.2.3 Bumps [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) from 6.2.2 to 6.2.3. - [Release notes](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases) - [Commits](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v6.2.2...v6.2.3) --- updated-dependencies: - dependency-name: Swashbuckle.AspNetCore dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 8a559704c06..39584ca693f 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -15,7 +15,7 @@ - + From b084afa1a04dd97bd5e9ffbae50e8d4848eec259 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Oct 2021 12:01:09 +0000 Subject: [PATCH 214/278] Bump prometheus-net.DotNetRuntime from 4.2.1 to 4.2.2 Bumps [prometheus-net.DotNetRuntime](https://github.com/djluck/prometheus-net.DotNetRuntime) from 4.2.1 to 4.2.2. - [Release notes](https://github.com/djluck/prometheus-net.DotNetRuntime/releases) - [Commits](https://github.com/djluck/prometheus-net.DotNetRuntime/compare/4.2.1...4.2.2) --- updated-dependencies: - dependency-name: prometheus-net.DotNetRuntime dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 9372cba9f20..c1ce4b55722 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -31,7 +31,7 @@ - + From dd0c1d3d27b455cc73d03bcc48becef298404dee Mon Sep 17 00:00:00 2001 From: Jessica Date: Mon, 18 Oct 2021 14:01:33 -0400 Subject: [PATCH 215/278] Added translation using Weblate (Welsh) --- Emby.Server.Implementations/Localization/Core/cy.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 Emby.Server.Implementations/Localization/Core/cy.json diff --git a/Emby.Server.Implementations/Localization/Core/cy.json b/Emby.Server.Implementations/Localization/Core/cy.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/cy.json @@ -0,0 +1 @@ +{} From 4d324707157d444efa43c21869f73b8378901dd3 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Mon, 18 Oct 2021 13:39:48 +0000 Subject: [PATCH 216/278] Translated using Weblate (Esperanto) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eo/ --- .../Localization/Core/eo.json | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json index ca615cc8cfd..a6516a130de 100644 --- a/Emby.Server.Implementations/Localization/Core/eo.json +++ b/Emby.Server.Implementations/Localization/Core/eo.json @@ -1,17 +1,17 @@ { "NotificationOptionInstallationFailed": "Instalada fiasko", - "NotificationOptionAudioPlaybackStopped": "Sono de ludado haltis", + "NotificationOptionAudioPlaybackStopped": "Ludado de sono haltis", "NotificationOptionAudioPlayback": "Ludado de sono startis", "NameSeasonUnknown": "Sezono Nekonata", "NameSeasonNumber": "Sezono {0}", "NameInstallFailed": "{0} instalado fiaskis", "Music": "Muziko", "Movies": "Filmoj", - "ItemRemovedWithName": "{0} forigis el la biblioteko", - "ItemAddedWithName": "{0} aldonis al la biblioteko", + "ItemRemovedWithName": "{0} forigis el la libraro", + "ItemAddedWithName": "{0} aldonis al la libraro", "HeaderLiveTV": "Viva Televido", "HeaderContinueWatching": "Daŭrigi Spektado", - "HeaderAlbumArtists": "Artistoj de Albumo", + "HeaderAlbumArtists": "Albumo de artisto", "Folders": "Dosierujoj", "DeviceOnlineWithName": "{0} estas konektita", "Default": "Defaŭlte", @@ -30,7 +30,7 @@ "User": "Uzanto", "System": "Sistemo", "Songs": "Kantoj", - "ScheduledTaskStartedWithName": "{0} komencis", + "ScheduledTaskStartedWithName": "{0} startis", "ScheduledTaskFailedWithName": "{0} malsukcesis", "PluginUninstalledWithName": "{0} malinstaliĝis", "PluginInstalledWithName": "{0} instaliĝis", @@ -43,5 +43,10 @@ "MusicVideos": "Muzikvideoj", "LabelIpAddressValue": "IP-adreso: {0}", "Genres": "Ĝenroj", - "DeviceOfflineWithName": "{0} malkonektis" + "DeviceOfflineWithName": "{0} malkonektis", + "HeaderFavoriteArtists": "Favorataj Artistoj", + "Shows": "Serioj", + "HeaderFavoriteShows": "Favorataj Serioj", + "TvShows": "TV-serioj", + "Favorites": "Favoratoj" } From 8388f7c462d50065a931a42ff2c7b34a0d7e1c34 Mon Sep 17 00:00:00 2001 From: Jessica Date: Mon, 18 Oct 2021 18:19:17 +0000 Subject: [PATCH 217/278] Translated using Weblate (Welsh) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cy/ --- .../Localization/Core/cy.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/cy.json b/Emby.Server.Implementations/Localization/Core/cy.json index 0967ef424bc..0fa72dea413 100644 --- a/Emby.Server.Implementations/Localization/Core/cy.json +++ b/Emby.Server.Implementations/Localization/Core/cy.json @@ -1 +1,14 @@ -{} +{ + "DeviceOnlineWithName": "Mae {0} wedi'i gysylltu", + "DeviceOfflineWithName": "Mae {0} wedi datgysylltu", + "Default": "Diofyn", + "Collections": "Casgliadau", + "ChapterNameValue": "Pennod {0}", + "Channels": "Sianeli", + "CameraImageUploadedFrom": "Mae delwedd camera newydd wedi'i lanlwytho o {0}", + "Books": "Llyfrau", + "AuthenticationSucceededWithUserName": "{0} wedi’i ddilysu’n llwyddiannus", + "Artists": "Artistiaid", + "AppDeviceValues": "Ap: {0}, Dyfais: {1}", + "Albums": "Albwmau" +} From ade3afad418f6fb29b2834d515a851ff3941a511 Mon Sep 17 00:00:00 2001 From: MBR-0001 <55142207+MBR-0001@users.noreply.github.com> Date: Tue, 19 Oct 2021 21:06:05 +0200 Subject: [PATCH 218/278] Add IsAutomated to SubtitleSearchRequest --- CONTRIBUTORS.md | 1 + Jellyfin.Api/Controllers/SubtitleController.cs | 2 +- MediaBrowser.Controller/Subtitles/ISubtitleManager.cs | 2 ++ .../Subtitles/SubtitleSearchRequest.cs | 2 ++ MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 1 + MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs | 8 +++++++- MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs | 1 + MediaBrowser.Providers/Subtitles/SubtitleManager.cs | 5 +++-- 8 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index cb52cafedf8..5c4a031bc14 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -149,6 +149,7 @@ - [skyfrk](https://github.com/skyfrk) - [ianjazz246](https://github.com/ianjazz246) - [peterspenler](https://github.com/peterspenler) + - [MBR-0001](https://github.com/MBR-0001) # Emby Contributors diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 8fb85c732b1..db8307f284b 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -127,7 +127,7 @@ namespace Jellyfin.Api.Controllers { var video = (Video)_libraryManager.GetItemById(itemId); - return await _subtitleManager.SearchSubtitles(video, language, isPerfectMatch, CancellationToken.None).ConfigureAwait(false); + return await _subtitleManager.SearchSubtitles(video, language, isPerfectMatch, false, CancellationToken.None).ConfigureAwait(false); } /// diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs index 3330dd5408d..52aa44024d1 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs @@ -31,12 +31,14 @@ namespace MediaBrowser.Controller.Subtitles /// The video. /// Subtitle language. /// Require perfect match. + /// Request is automated. /// CancellationToken to use for the operation. /// Subtitles, wrapped in task. Task SearchSubtitles( Video video, string language, bool? isPerfectMatch, + bool isAutomated, CancellationToken cancellationToken); /// diff --git a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs index 767d87d4659..ef052237afe 100644 --- a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs +++ b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs @@ -51,5 +51,7 @@ namespace MediaBrowser.Controller.Subtitles public string[] DisabledSubtitleFetchers { get; set; } public string[] SubtitleFetcherOrder { get; set; } + + public bool IsAutomated { get; set; } } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 1f17d8cd4ce..261c012a1e2 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -557,6 +557,7 @@ namespace MediaBrowser.Providers.MediaInfo subtitleDownloadLanguages, libraryOptions.DisabledSubtitleFetchers, libraryOptions.SubtitleFetcherOrder, + true, cancellationToken).ConfigureAwait(false); // Rescan diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs index 449f0d25915..cba2f1c421d 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs @@ -36,6 +36,7 @@ namespace MediaBrowser.Providers.MediaInfo IEnumerable languages, string[] disabledSubtitleFetchers, string[] subtitleFetcherOrder, + bool isAutomated, CancellationToken cancellationToken) { var downloadedLanguages = new List(); @@ -51,6 +52,7 @@ namespace MediaBrowser.Providers.MediaInfo lang, disabledSubtitleFetchers, subtitleFetcherOrder, + isAutomated, cancellationToken).ConfigureAwait(false); if (downloaded) @@ -71,6 +73,7 @@ namespace MediaBrowser.Providers.MediaInfo string lang, string[] disabledSubtitleFetchers, string[] subtitleFetcherOrder, + bool isAutomated, CancellationToken cancellationToken) { if (video.VideoType != VideoType.VideoFile) @@ -109,6 +112,7 @@ namespace MediaBrowser.Providers.MediaInfo disabledSubtitleFetchers, subtitleFetcherOrder, mediaType, + isAutomated, cancellationToken); } @@ -122,6 +126,7 @@ namespace MediaBrowser.Providers.MediaInfo string[] disabledSubtitleFetchers, string[] subtitleFetcherOrder, VideoContentType mediaType, + bool isAutomated, CancellationToken cancellationToken) { // There's already subtitles for this language @@ -169,7 +174,8 @@ namespace MediaBrowser.Providers.MediaInfo IsPerfectMatch = requirePerfectMatch, DisabledSubtitleFetchers = disabledSubtitleFetchers, - SubtitleFetcherOrder = subtitleFetcherOrder + SubtitleFetcherOrder = subtitleFetcherOrder, + IsAutomated = isAutomated }; if (video is Episode episode) diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 9804ec3bb46..4dfe2c59f70 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -197,6 +197,7 @@ namespace MediaBrowser.Providers.MediaInfo subtitleDownloadLanguages, libraryOptions.DisabledSubtitleFetchers, libraryOptions.SubtitleFetcherOrder, + true, cancellationToken).ConfigureAwait(false); // Rescan diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index b7074a04c1b..73f9d7b6270 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -274,7 +274,7 @@ namespace MediaBrowser.Providers.Subtitles } /// - public Task SearchSubtitles(Video video, string language, bool? isPerfectMatch, CancellationToken cancellationToken) + public Task SearchSubtitles(Video video, string language, bool? isPerfectMatch, bool isAutomated, CancellationToken cancellationToken) { if (video.VideoType != VideoType.VideoFile) { @@ -308,7 +308,8 @@ namespace MediaBrowser.Providers.Subtitles ProductionYear = video.ProductionYear, ProviderIds = video.ProviderIds, RuntimeTicks = video.RunTimeTicks, - IsPerfectMatch = isPerfectMatch ?? false + IsPerfectMatch = isPerfectMatch ?? false, + IsAutomated = isAutomated }; if (video is Episode episode) From 7d8fb947b65638c41a9f6475306a1b81c577f089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sindre=20S=2E=20Kj=C3=A6r?= Date: Tue, 19 Oct 2021 19:19:17 +0000 Subject: [PATCH 219/278] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)=20Translation:=20Jellyfin/Jellyfin=20Translat?= =?UTF-8?q?e-URL:=20https://translate.jellyfin.org/projects/jellyfin/jelly?= =?UTF-8?q?fin-core/nb=5FNO/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Emby.Server.Implementations/Localization/Core/nb.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index 81c1eefe743..317bdcfcb62 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -119,5 +119,6 @@ "Forced": "Tvunget", "Default": "Standard", "TaskCleanActivityLogDescription": "Sletter oppføringer i aktivitetsloggen som er eldre enn den konfigurerte alderen.", - "TaskOptimizeDatabase": "Optimiser database" + "TaskOptimizeDatabase": "Optimiser database", + "TaskOptimizeDatabaseDescription": "Komprimerer database og frigjør plass. Denne prosessen kan forbedre ytelsen etter skanning av bibliotek eller andre handlinger som fører til databaseendringer." } From 31baea072a1ed25346437150f27a93916cb4cca0 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Mon, 11 Oct 2021 21:25:12 +0200 Subject: [PATCH 220/278] Address review comments Clean up style Fix references in class summaries Combine Where+FirstOrDefault queries Break up large method, long lines Add validation on file extension Apply test naming conventions Extract mock of Movie class, comment on why not mocking interface Co-authored-by: Cody Robibero Co-authored-by: Claus Vium --- .../Encoder/MediaEncoder.cs | 9 + .../Probing/ProbeResultNormalizer.cs | 4 +- .../MediaInfo/EmbeddedImageProvider.cs | 95 ++++++----- .../MediaInfo/VideoImageProvider.cs | 10 +- .../MediaInfo/EmbeddedImageProviderTests.cs | 155 +++++++++--------- .../MediaInfo/VideoImageProviderTests.cs | 108 ++++++------ 6 files changed, 208 insertions(+), 173 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index dac2c6a26a3..102b4f943ee 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -548,6 +548,15 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new ArgumentNullException(nameof(inputPath)); } + if (string.IsNullOrEmpty(outputExtension)) + { + outputExtension = ".jpg"; + } + else if (outputExtension[0] != '.') + { + outputExtension = "." + outputExtension; + } + var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + outputExtension); Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath)); diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 775689095fe..9279cb220af 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -582,8 +582,8 @@ namespace MediaBrowser.MediaEncoding.Probing /// MediaAttachments. private MediaAttachment GetMediaAttachment(MediaStreamInfo streamInfo) { - if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase) && - !(streamInfo.Disposition != null && streamInfo.Disposition.GetValueOrDefault("attached_pic") == 1)) + if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase) + && streamInfo.Disposition?.GetValueOrDefault("attached_pic") != 1) { return null; } diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs index df87f2d49cc..1d9d1e02a54 100644 --- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.IO; @@ -19,7 +17,7 @@ using MediaBrowser.Model.Net; namespace MediaBrowser.Providers.MediaInfo { /// - /// Uses ffmpeg to extract embedded images. + /// Uses to extract embedded images. /// public class EmbeddedImageProvider : IDynamicImageProvider, IHasOrder { @@ -46,6 +44,10 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IMediaEncoder _mediaEncoder; + /// + /// Initializes a new instance of the class. + /// + /// The media encoder for extracting attached/embedded images. public EmbeddedImageProvider(IMediaEncoder mediaEncoder) { _mediaEncoder = mediaEncoder; @@ -65,13 +67,13 @@ namespace MediaBrowser.Providers.MediaInfo { if (item is Episode) { - return new List + return new[] { ImageType.Primary, }; } - return new List + return new[] { ImageType.Primary, ImageType.Backdrop, @@ -79,7 +81,7 @@ namespace MediaBrowser.Providers.MediaInfo }; } - return new List(); + return Array.Empty(); } /// @@ -114,47 +116,20 @@ namespace MediaBrowser.Providers.MediaInfo }; // Try attachments first - var attachmentSources = item.GetMediaSources(false).SelectMany(source => source.MediaAttachments).ToList(); - var attachmentStream = attachmentSources - .Where(attachment => !string.IsNullOrEmpty(attachment.FileName)) - .FirstOrDefault(attachment => imageFileNames.Any(name => attachment.FileName.Contains(name, StringComparison.OrdinalIgnoreCase))); + var attachmentStream = item.GetMediaSources(false) + .SelectMany(source => source.MediaAttachments) + .FirstOrDefault(attachment => !string.IsNullOrEmpty(attachment.FileName) + && imageFileNames.Any(name => attachment.FileName.Contains(name, StringComparison.OrdinalIgnoreCase))); if (attachmentStream != null) { - var extension = string.IsNullOrEmpty(attachmentStream.MimeType) ? - Path.GetExtension(attachmentStream.FileName) : - MimeTypes.ToExtension(attachmentStream.MimeType); - - if (string.IsNullOrEmpty(extension)) - { - extension = ".jpg"; - } - - string extractedAttachmentPath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken).ConfigureAwait(false); - - ImageFormat format = extension switch - { - ".bmp" => ImageFormat.Bmp, - ".gif" => ImageFormat.Gif, - ".jpg" => ImageFormat.Jpg, - ".png" => ImageFormat.Png, - ".webp" => ImageFormat.Webp, - _ => ImageFormat.Jpg - }; - - return new DynamicImageResponse - { - Format = format, - HasImage = true, - Path = extractedAttachmentPath, - Protocol = MediaProtocol.File - }; + return await ExtractAttachment(item, cancellationToken, attachmentStream, mediaSource); } // Fall back to EmbeddedImage streams var imageStreams = item.GetMediaStreams().FindAll(i => i.Type == MediaStreamType.EmbeddedImage); - if (!imageStreams.Any()) + if (imageStreams.Count == 0) { // Can't extract if we don't have any EmbeddedImage streams return new DynamicImageResponse { HasImage = false }; @@ -162,8 +137,8 @@ namespace MediaBrowser.Providers.MediaInfo // Extract first stream containing an element of imageFileNames var imageStream = imageStreams - .Where(stream => !string.IsNullOrEmpty(stream.Comment)) - .FirstOrDefault(stream => imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase))); + .FirstOrDefault(stream => !string.IsNullOrEmpty(stream.Comment) + && imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase))); // Primary type only: default to first image if none found by label if (imageStream == null) @@ -179,7 +154,9 @@ namespace MediaBrowser.Providers.MediaInfo } } - string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, "jpg", cancellationToken).ConfigureAwait(false); + string extractedImagePath = + await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, ".jpg", cancellationToken) + .ConfigureAwait(false); return new DynamicImageResponse { @@ -190,6 +167,40 @@ namespace MediaBrowser.Providers.MediaInfo }; } + private async Task ExtractAttachment(Video item, CancellationToken cancellationToken, MediaAttachment attachmentStream, MediaSourceInfo mediaSource) + { + var extension = string.IsNullOrEmpty(attachmentStream.MimeType) + ? Path.GetExtension(attachmentStream.FileName) + : MimeTypes.ToExtension(attachmentStream.MimeType); + + if (string.IsNullOrEmpty(extension)) + { + extension = ".jpg"; + } + + string extractedAttachmentPath = + await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken) + .ConfigureAwait(false); + + ImageFormat format = extension switch + { + ".bmp" => ImageFormat.Bmp, + ".gif" => ImageFormat.Gif, + ".jpg" => ImageFormat.Jpg, + ".png" => ImageFormat.Png, + ".webp" => ImageFormat.Webp, + _ => ImageFormat.Jpg + }; + + return new DynamicImageResponse + { + Format = format, + HasImage = true, + Path = extractedAttachmentPath, + Protocol = MediaProtocol.File + }; + } + /// public bool Supports(BaseItem item) { diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 60739f15640..d226182c09d 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -1,6 +1,3 @@ -#nullable enable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Linq; @@ -18,13 +15,18 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.MediaInfo { /// - /// Uses ffmpeg to create still images from the main video. + /// Uses to create still images from the main video. /// public class VideoImageProvider : IDynamicImageProvider, IHasOrder { private readonly IMediaEncoder _mediaEncoder; private readonly ILogger _logger; + /// + /// Initializes a new instance of the class. + /// + /// The media encoder for capturing images. + /// The logger. public VideoImageProvider(IMediaEncoder mediaEncoder, ILogger logger) { _mediaEncoder = mediaEncoder; diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs index fcea1532af5..b194e38855a 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs @@ -17,38 +17,37 @@ namespace Jellyfin.Providers.Tests.MediaInfo { public class EmbeddedImageProviderTests { - public static TheoryData GetSupportedImages_Empty_TestData => - new () + private static TheoryData GetSupportedImages_UnsupportedBaseItems_ReturnsEmpty_TestData() + { + return new () { new AudioBook(), new BoxSet(), new Series(), new Season(), }; + } - public static TheoryData> GetSupportedImages_Populated_TestData => - new TheoryData> + [Theory] + [MemberData(nameof(GetSupportedImages_UnsupportedBaseItems_ReturnsEmpty_TestData))] + public void GetSupportedImages_UnsupportedBaseItems_ReturnsEmpty(BaseItem item) + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + Assert.Empty(embeddedImageProvider.GetSupportedImages(item)); + } + + private static TheoryData> GetSupportedImages_SupportedBaseItems_ReturnsPopulated_TestData() + { + return new TheoryData> { { new Episode(), new List { ImageType.Primary } }, { new Movie(), new List { ImageType.Logo, ImageType.Backdrop, ImageType.Primary } }, }; - - private EmbeddedImageProvider GetEmbeddedImageProvider(IMediaEncoder? mediaEncoder) - { - return new EmbeddedImageProvider(mediaEncoder); } [Theory] - [MemberData(nameof(GetSupportedImages_Empty_TestData))] - public void GetSupportedImages_Empty(BaseItem item) - { - var embeddedImageProvider = GetEmbeddedImageProvider(null); - Assert.False(embeddedImageProvider.GetSupportedImages(item).Any()); - } - - [Theory] - [MemberData(nameof(GetSupportedImages_Populated_TestData))] - public void GetSupportedImages_Populated(BaseItem item, IEnumerable expected) + [MemberData(nameof(GetSupportedImages_SupportedBaseItems_ReturnsPopulated_TestData))] + public void GetSupportedImages_SupportedBaseItems_ReturnsPopulated(BaseItem item, IEnumerable expected) { var embeddedImageProvider = GetEmbeddedImageProvider(null); var actual = embeddedImageProvider.GetSupportedImages(item); @@ -56,56 +55,34 @@ namespace Jellyfin.Providers.Tests.MediaInfo } [Fact] - public async void GetImage_Empty_NoStreams() + public async void GetImage_InputWithNoStreams_ReturnsNoImage() { var embeddedImageProvider = GetEmbeddedImageProvider(null); - var input = new Mock(); - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List()); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List()); + var input = GetMovie(new List(), new List()); - var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.False(actual.HasImage); } [Fact] - public async void GetImage_Empty_NoLabeledAttachments() + public async void GetImage_InputWithUnlabeledAttachments_ReturnsNoImage() { var embeddedImageProvider = GetEmbeddedImageProvider(null); - var input = new Mock(); // add an attachment without a filename - has a list to look through but finds nothing - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List { new () { MediaAttachments = new List { new () } } }); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List()); + var input = GetMovie( + new List { new () }, + new List()); - var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.False(actual.HasImage); } [Fact] - public async void GetImage_Empty_NoEmbeddedLabeledBackdrop() - { - var embeddedImageProvider = GetEmbeddedImageProvider(null); - - var input = new Mock(); - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List()); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List { new () { Type = MediaStreamType.EmbeddedImage } }); - - var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); - Assert.NotNull(actual); - Assert.False(actual.HasImage); - } - - [Fact] - public async void GetImage_Attached() + public async void GetImage_InputWithLabeledAttachments_ReturnsCorrectSelection() { // first tests file extension detection, second uses mimetype, third defaults to jpg MediaAttachment sampleAttachment1 = new () { FileName = "clearlogo.png", Index = 1 }; @@ -124,25 +101,23 @@ namespace Jellyfin.Providers.Tests.MediaInfo .Returns(Task.FromResult(targetPath3)); var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List { new () { MediaAttachments = new List { sampleAttachment1, sampleAttachment2, sampleAttachment3 } } }); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List()); + var input = GetMovie( + new List { sampleAttachment1, sampleAttachment2, sampleAttachment3 }, + new List()); - var actualLogo = await embeddedImageProvider.GetImage(input.Object, ImageType.Logo, CancellationToken.None); + var actualLogo = await embeddedImageProvider.GetImage(input, ImageType.Logo, CancellationToken.None); Assert.NotNull(actualLogo); Assert.True(actualLogo.HasImage); Assert.Equal(targetPath1, actualLogo.Path); Assert.Equal(ImageFormat.Png, actualLogo.Format); - var actualBackdrop = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); + var actualBackdrop = await embeddedImageProvider.GetImage(input, ImageType.Backdrop, CancellationToken.None); Assert.NotNull(actualBackdrop); Assert.True(actualBackdrop.HasImage); Assert.Equal(targetPath2, actualBackdrop.Path); Assert.Equal(ImageFormat.Bmp, actualBackdrop.Format); - var actualPrimary = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actualPrimary = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actualPrimary); Assert.True(actualPrimary.HasImage); Assert.Equal(targetPath3, actualPrimary.Path); @@ -150,23 +125,35 @@ namespace Jellyfin.Providers.Tests.MediaInfo } [Fact] - public async void GetImage_EmbeddedDefault() + public async void GetImage_InputWithUnlabeledEmbeddedImages_BackdropReturnsNoImage() + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + + var input = GetMovie( + new List(), + new List { new () { Type = MediaStreamType.EmbeddedImage } }); + + var actual = await embeddedImageProvider.GetImage(input, ImageType.Backdrop, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_InputWithUnlabeledEmbeddedImages_PrimaryReturnsImage() { MediaStream sampleStream = new () { Type = MediaStreamType.EmbeddedImage, Index = 1 }; string targetPath = "path"; var mediaEncoder = new Mock(MockBehavior.Strict); - mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream, 1, "jpg", CancellationToken.None)) + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream, 1, ".jpg", CancellationToken.None)) .Returns(Task.FromResult(targetPath)); var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List()); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List() { sampleStream }); + var input = GetMovie( + new List(), + new List { sampleStream }); - var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.True(actual.HasImage); Assert.Equal(targetPath, actual.Path); @@ -174,7 +161,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo } [Fact] - public async void GetImage_EmbeddedSelection() + public async void GetImage_InputWithLabeledEmbeddedImages_ReturnsCorrectSelection() { // primary is second stream to ensure it's not defaulting, backdrop is first MediaStream sampleStream1 = new () { Type = MediaStreamType.EmbeddedImage, Index = 1, Comment = "backdrop" }; @@ -183,29 +170,47 @@ namespace Jellyfin.Providers.Tests.MediaInfo string targetPath2 = "path2.jpg"; var mediaEncoder = new Mock(MockBehavior.Strict); - mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream1, 1, "jpg", CancellationToken.None)) + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream1, 1, ".jpg", CancellationToken.None)) .Returns(Task.FromResult(targetPath1)); - mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream2, 2, "jpg", CancellationToken.None)) + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream2, 2, ".jpg", CancellationToken.None)) .Returns(Task.FromResult(targetPath2)); var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List()); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List { sampleStream1, sampleStream2 }); + var input = GetMovie( + new List(), + new List { sampleStream1, sampleStream2 }); - var actualPrimary = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actualPrimary = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actualPrimary); Assert.True(actualPrimary.HasImage); Assert.Equal(targetPath2, actualPrimary.Path); Assert.Equal(ImageFormat.Jpg, actualPrimary.Format); - var actualBackdrop = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); + var actualBackdrop = await embeddedImageProvider.GetImage(input, ImageType.Backdrop, CancellationToken.None); Assert.NotNull(actualBackdrop); Assert.True(actualBackdrop.HasImage); Assert.Equal(targetPath1, actualBackdrop.Path); Assert.Equal(ImageFormat.Jpg, actualBackdrop.Format); } + + private static EmbeddedImageProvider GetEmbeddedImageProvider(IMediaEncoder? mediaEncoder) + { + return new EmbeddedImageProvider(mediaEncoder); + } + + private static Movie GetMovie(List mediaAttachments, List mediaStreams) + { + // Mocking IMediaSourceManager GetMediaAttachments and GetMediaStreams instead of mocking Movie works, but + // has concurrency problems between this and VideoImageProviderTests due to BaseItem.MediaSourceManager + // being static + var movie = new Mock(); + + movie.Setup(item => item.GetMediaSources(It.IsAny())) + .Returns(new List { new () { MediaAttachments = mediaAttachments } } ); + movie.Setup(item => item.GetMediaStreams()) + .Returns(mediaStreams); + + return movie.Object; + } } } diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs index 9a5cd79bbff..0f51a2b8f8b 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs @@ -16,56 +16,51 @@ namespace Jellyfin.Providers.Tests.MediaInfo { public class VideoImageProviderTests { - private VideoImageProvider GetVideoImageProvider(IMediaEncoder? mediaEncoder) - { - // strict to ensure this isn't accidentally used where a prepared mock is intended - mediaEncoder ??= new Mock(MockBehavior.Strict).Object; - return new VideoImageProvider(mediaEncoder, new NullLogger()); - } - [Fact] - public async void GetImage_Empty_IsPlaceholder() + public async void GetImage_InputIsPlaceholder_ReturnsNoImage() { var videoImageProvider = GetVideoImageProvider(null); - var input = new Mock(); - input.Object.IsPlaceHolder = true; + var input = new Movie + { + IsPlaceHolder = true + }; - var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.False(actual.HasImage); } [Fact] - public async void GetImage_Empty_NoDefaultVideoStream() + public async void GetImage_NoDefaultVideoStream_ReturnsNoImage() { var videoImageProvider = GetVideoImageProvider(null); - var input = new Mock(); + var input = new Movie + { + DefaultVideoStreamIndex = null + }; - var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.False(actual.HasImage); } [Fact] - public async void GetImage_Empty_DefaultSet_NoVideoStream() + public async void GetImage_DefaultSetButNoVideoStream_ReturnsNoImage() { var videoImageProvider = GetVideoImageProvider(null); - var input = new Mock(); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List()); // set a default index but don't put anything there (invalid input, but provider shouldn't break) - input.Object.DefaultVideoStreamIndex = 1; + var input = GetMovie(0, null, new List()); - var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.False(actual.HasImage); } [Fact] - public async void GetImage_Extract_DefaultStream() + public async void GetImage_DefaultSetMultipleVideoStreams_ReturnsDefaultStreamImage() { MediaStream firstStream = new () { Type = MediaStreamType.Video, Index = 0 }; MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 1 }; @@ -78,14 +73,9 @@ namespace Jellyfin.Providers.Tests.MediaInfo .Returns(Task.FromResult(targetPath)); var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetDefaultVideoStream()) - .Returns(targetStream); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List() { firstStream, targetStream }); - input.Object.DefaultVideoStreamIndex = 1; + var input = GetMovie(1, targetStream, new List { firstStream, targetStream } ); - var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.True(actual.HasImage); Assert.Equal(targetPath, actual.Path); @@ -93,7 +83,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo } [Fact] - public async void GetImage_Extract_FallbackToFirstVideoStream() + public async void GetImage_InvalidDefaultSingleVideoStream_ReturnsFirstVideoStreamImage() { MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; string targetPath = "path.jpg"; @@ -103,13 +93,10 @@ namespace Jellyfin.Providers.Tests.MediaInfo .Returns(Task.FromResult(targetPath)); var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List() { targetStream }); - // default must be set, ensure a stream is still found if not pointed at a video - input.Object.DefaultVideoStreamIndex = 5; + // provide query results for default (empty) and all streams (populated) + var input = GetMovie(5, null, new List { targetStream }); - var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.True(actual.HasImage); Assert.Equal(targetPath, actual.Path); @@ -117,10 +104,12 @@ namespace Jellyfin.Providers.Tests.MediaInfo } [Fact] - public async void GetImage_Time_Default() + public async void GetImage_NoTimeSpanSet_CallsEncoderWithDefaultTime() { MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; + // use a callback to catch the actual value + // provides more information on failure than verifying a specific input was called on the mock TimeSpan? actualTimeSpan = null; var mediaEncoder = new Mock(MockBehavior.Strict); mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), CancellationToken.None)) @@ -128,20 +117,16 @@ namespace Jellyfin.Providers.Tests.MediaInfo .Returns(Task.FromResult("path")); var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List() { targetStream }); - // default must be set - input.Object.DefaultVideoStreamIndex = 0; + var input = GetMovie(0, targetStream, new List { targetStream }); // not testing return, just verifying what gets requested for time span - await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.Equal(TimeSpan.FromSeconds(10), actualTimeSpan); } [Fact] - public async void GetImage_Time_Calculated() + public async void GetImage_TimeSpanSet_CallsEncoderWithCalculatedTime() { MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; @@ -152,17 +137,40 @@ namespace Jellyfin.Providers.Tests.MediaInfo .Returns(Task.FromResult("path")); var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List() { targetStream }); - // default must be set - input.Object.DefaultVideoStreamIndex = 0; - input.Object.RunTimeTicks = 5000; + var input = GetMovie(0, targetStream, new List { targetStream }); + input.RunTimeTicks = 5000; // not testing return, just verifying what gets requested for time span - await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.Equal(TimeSpan.FromTicks(500), actualTimeSpan); } + + private static VideoImageProvider GetVideoImageProvider(IMediaEncoder? mediaEncoder) + { + // strict to ensure this isn't accidentally used where a prepared mock is intended + mediaEncoder ??= new Mock(MockBehavior.Strict).Object; + return new VideoImageProvider(mediaEncoder, new NullLogger()); + } + + private static Movie GetMovie(int defaultVideoStreamIndex, MediaStream? defaultStream, List mediaStreams) + { + // Mocking IMediaSourceManager GetMediaStreams instead of mocking Movie works, but has concurrency problems + // between this and EmbeddedImageProviderTests due to BaseItem.MediaSourceManager being static + var movie = new Mock + { + Object = + { + DefaultVideoStreamIndex = defaultVideoStreamIndex + } + }; + + movie.Setup(item => item.GetDefaultVideoStream()) + .Returns(defaultStream!); + movie.Setup(item => item.GetMediaStreams()) + .Returns(mediaStreams); + + return movie.Object; + } } } From 1cf2ec26a3b81d2a5495a520d8638fbc66fd17c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Oct 2021 12:24:31 +0000 Subject: [PATCH 221/278] Bump Swashbuckle.AspNetCore.ReDoc from 6.2.2 to 6.2.3 Bumps [Swashbuckle.AspNetCore.ReDoc](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) from 6.2.2 to 6.2.3. - [Release notes](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases) - [Commits](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v6.2.2...v6.2.3) --- updated-dependencies: - dependency-name: Swashbuckle.AspNetCore.ReDoc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 39584ca693f..57480b2f358 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -16,7 +16,7 @@ - + From b53dea7113726c052a13e3cfa309730787db22cb Mon Sep 17 00:00:00 2001 From: WWWesten Date: Thu, 21 Oct 2021 11:21:38 +0000 Subject: [PATCH 222/278] Translated using Weblate (Esperanto) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eo/ --- .../Localization/Core/eo.json | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json index a6516a130de..e04f3c2414d 100644 --- a/Emby.Server.Implementations/Localization/Core/eo.json +++ b/Emby.Server.Implementations/Localization/Core/eo.json @@ -10,7 +10,7 @@ "ItemRemovedWithName": "{0} forigis el la libraro", "ItemAddedWithName": "{0} aldonis al la libraro", "HeaderLiveTV": "Viva Televido", - "HeaderContinueWatching": "Daŭrigi Spektado", + "HeaderContinueWatching": "Daŭrigi Spektadon", "HeaderAlbumArtists": "Albumo de artisto", "Folders": "Dosierujoj", "DeviceOnlineWithName": "{0} estas konektita", @@ -48,5 +48,28 @@ "Shows": "Serioj", "HeaderFavoriteShows": "Favorataj Serioj", "TvShows": "TV-serioj", - "Favorites": "Favoratoj" + "Favorites": "Favoratoj", + "TaskCleanLogs": "Purigi Ĵurnalan Katalogon", + "TaskRefreshLibrary": "Skanu Plurmedian Libraron", + "ValueSpecialEpisodeName": "Speciala - {0}", + "TaskOptimizeDatabase": "Optimigi datumbazon", + "TaskRefreshChannels": "Refreŝigi Kanalojn", + "TaskUpdatePlugins": "Ĝisdatigi Kromprogramojn", + "TaskRefreshPeople": "Refreŝigi Homojn", + "TasksChannelsCategory": "Interretaj Kanaloj", + "ProviderValue": "Provizanto: {0}", + "NotificationOptionPluginError": "Kromprograma malsukceso", + "MixedContent": "Miksita enhavo", + "TasksApplicationCategory": "Aplikaĵo", + "TasksMaintenanceCategory": "Prizorgado", + "Undefined": "Nedifinita", + "Sync": "Sinkronigo", + "Latest": "Plej novaj", + "Inherit": "Hereda", + "HomeVideos": "Hejmaj Videoj", + "HeaderNextUp": "Sekva Plue", + "HeaderFavoriteSongs": "Favorataj Kantoj", + "HeaderFavoriteEpisodes": "Favorataj Epizodoj", + "HeaderFavoriteAlbums": "Favorataj Albumoj", + "Forced": "Forcita" } From 1e69530752d52af9e091d5325a7bd01d125cbe25 Mon Sep 17 00:00:00 2001 From: rimasx Date: Wed, 20 Oct 2021 04:30:06 +0000 Subject: [PATCH 223/278] Translated using Weblate (Estonian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/et/ --- Emby.Server.Implementations/Localization/Core/et.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json index 3e57d96ca29..c3596ecf143 100644 --- a/Emby.Server.Implementations/Localization/Core/et.json +++ b/Emby.Server.Implementations/Localization/Core/et.json @@ -45,7 +45,7 @@ "System": "Süsteem", "Sync": "Sünkrooni", "Songs": "Laulud", - "Shows": "Seriaalid", + "Shows": "Sarjad", "ServerNameNeedsToBeRestarted": "{0} tuleb taaskäivitada", "ScheduledTaskFailedWithName": "{0} nurjus", "PluginUpdatedWithName": "{0} uuendati", From d4c1912e64f5e9d82f2106f1c9ca47d6addf16fa Mon Sep 17 00:00:00 2001 From: WWWesten Date: Sat, 23 Oct 2021 10:15:08 +0000 Subject: [PATCH 224/278] Translated using Weblate (Esperanto) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eo/ --- .../Localization/Core/eo.json | 56 +++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json index e04f3c2414d..7ce87502cc6 100644 --- a/Emby.Server.Implementations/Localization/Core/eo.json +++ b/Emby.Server.Implementations/Localization/Core/eo.json @@ -1,7 +1,7 @@ { "NotificationOptionInstallationFailed": "Instalada fiasko", "NotificationOptionAudioPlaybackStopped": "Ludado de sono haltis", - "NotificationOptionAudioPlayback": "Ludado de sono startis", + "NotificationOptionAudioPlayback": "Ludado de sono lanĉis", "NameSeasonUnknown": "Sezono Nekonata", "NameSeasonNumber": "Sezono {0}", "NameInstallFailed": "{0} instalado fiaskis", @@ -9,7 +9,7 @@ "Movies": "Filmoj", "ItemRemovedWithName": "{0} forigis el la libraro", "ItemAddedWithName": "{0} aldonis al la libraro", - "HeaderLiveTV": "Viva Televido", + "HeaderLiveTV": "TV-etero", "HeaderContinueWatching": "Daŭrigi Spektadon", "HeaderAlbumArtists": "Albumo de artisto", "Folders": "Dosierujoj", @@ -30,7 +30,7 @@ "User": "Uzanto", "System": "Sistemo", "Songs": "Kantoj", - "ScheduledTaskStartedWithName": "{0} startis", + "ScheduledTaskStartedWithName": "{0} lanĉis", "ScheduledTaskFailedWithName": "{0} malsukcesis", "PluginUninstalledWithName": "{0} malinstaliĝis", "PluginInstalledWithName": "{0} instaliĝis", @@ -71,5 +71,53 @@ "HeaderFavoriteSongs": "Favorataj Kantoj", "HeaderFavoriteEpisodes": "Favorataj Epizodoj", "HeaderFavoriteAlbums": "Favorataj Albumoj", - "Forced": "Forcita" + "Forced": "Forcita", + "ServerNameNeedsToBeRestarted": "{0} devas esti relanĉita", + "NotificationOptionVideoPlayback": "La videoludado lanĉis", + "NotificationOptionServerRestartRequired": "Servila relanĉigo bezonata", + "TaskOptimizeDatabaseDescription": "Kompaktigas datenbazon kaj trunkas liberan lokon. Lanĉi ĉi tiun taskon post la librara skanado aŭ fari aliajn ŝanĝojn, kiuj implicas datenbazajn modifojn, povus plibonigi rendimenton.", + "TaskUpdatePluginsDescription": "Elŝutas kaj instalas ĝisdatigojn por kromprogramojn, kiuj estas agorditaj por ĝisdatigi aŭtomate.", + "TaskDownloadMissingSubtitlesDescription": "Serĉas en interreto mankantajn subtekstojn surbaze de metadatena agordaro.", + "TaskRefreshPeopleDescription": "Ĝisdatigas metadatenojn por aktoroj kaj reĵisoroj en via plurmedia libraro.", + "TaskCleanLogsDescription": "Forigas ĵurnalajn dosierojn aĝajn pli ol {0} tagojn.", + "TaskRefreshLibraryDescription": "Skanas vian plurmedian libraron por novaj dosieroj kaj refreŝigas metadatenaron.", + "NewVersionIsAvailable": "Nova versio de Jellyfin Server estas elŝutebla.", + "TaskCleanCacheDescription": "Forigas stapla dosierojn ne plu necesajn de la sistemo.", + "TaskCleanActivityLogDescription": "Forigas aktivecan ĵurnalaĵojn pli malnovajn ol la agordita aĝo.", + "TaskCleanTranscodeDescription": "Forigas transkodajn dosierojn aĝajn pli ol unu tagon.", + "ValueHasBeenAddedToLibrary": "{0} estis aldonita al via plurmedia libraro", + "SubtitleDownloadFailureFromForItem": "Subtekstoj malsukcesis elŝuti de {0} por {1}", + "StartupEmbyServerIsLoading": "Jellyfin Server ŝarĝas. Provi denove baldaŭ.", + "TaskRefreshChapterImagesDescription": "Kreas bildetojn por videoj kiuj havas ĉapitrojn.", + "UserStoppedPlayingItemWithValues": "{0} finis ludi {1} ĉe {2}", + "UserPolicyUpdatedWithName": "Uzanta politiko estis ĝisdatigita por {0}", + "UserPasswordChangedWithName": "Pasvorto estis ŝanĝita por uzanto {0}", + "UserStartedPlayingItemWithValues": "{0} ludas {1} ĉe {2}", + "UserLockedOutWithName": "Uzanto {0} estas elŝlosita", + "UserOnlineFromDevice": "{0} estas enreta de {1}", + "UserOfflineFromDevice": "{0} malkonektis de {1}", + "UserDeletedWithName": "Uzanto {0} estis forigita", + "MessageServerConfigurationUpdated": "Servila agordaro estis ĝisdatigita", + "MessageNamedServerConfigurationUpdatedWithValue": "Servila agorda sekcio {0} estis ĝisdatigita", + "MessageApplicationUpdatedTo": "Jellyfin Server estis ĝisdatigita al {0}", + "MessageApplicationUpdated": "Jellyfin Server estis ĝisdatigita", + "TaskRefreshChannelsDescription": "Refreŝigas informon pri interretaj kanaloj.", + "TaskDownloadMissingSubtitles": "Elŝutu mankantajn subtekstojn", + "TaskCleanTranscode": "Malplenigi Transkodadan Katalogon", + "TaskRefreshChapterImages": "Eltiru Ĉapitro-Bildojn", + "TaskCleanCache": "Malplenigi Staplan Katalogon", + "TaskCleanActivityLog": "Malplenigi Aktivecan Ĵurnalon", + "PluginUpdatedWithName": "{0} estis ĝisdatigita", + "NotificationOptionVideoPlaybackStopped": "La videoludado haltis", + "NotificationOptionUserLockedOut": "Uzanto ŝlosita", + "NotificationOptionTaskFailed": "Planita tasko malsukcesis", + "NotificationOptionPluginUpdateInstalled": "Ĝisdatigo de kromprogramo instalita", + "NotificationOptionCameraImageUploaded": "Kamera bildo alŝutita", + "NotificationOptionApplicationUpdateInstalled": "Aplikaĵa ĝisdatigo instalita", + "NotificationOptionApplicationUpdateAvailable": "Ĝisdatigo de aplikaĵo havebla", + "LabelRunningTimeValue": "Ludada tempo: {0}", + "HeaderRecordingGroups": "Rikordadaj Grupoj", + "FailedLoginAttemptWithUserName": "Malsukcesa ensaluta provo de {0}", + "CameraImageUploadedFrom": "Nova kamera bildo estis alŝutita de {0}", + "AuthenticationSucceededWithUserName": "{0} sukcese aŭtentikigis" } From f2656b7ee2ea9c395a796d2117ffe445a26d7603 Mon Sep 17 00:00:00 2001 From: ButterflyOfFire Date: Sat, 23 Oct 2021 11:25:19 +0000 Subject: [PATCH 225/278] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/ --- Emby.Server.Implementations/Localization/Core/ar.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index 5a4a4d5a917..ee70aa8d86f 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -15,7 +15,7 @@ "Favorites": "المفضلة", "Folders": "المجلدات", "Genres": "التضنيفات", - "HeaderAlbumArtists": "فناني الألبومات", + "HeaderAlbumArtists": "ألبوم الفنان", "HeaderContinueWatching": "استئناف", "HeaderFavoriteAlbums": "الألبومات المفضلة", "HeaderFavoriteArtists": "الفنانون المفضلون", @@ -25,7 +25,7 @@ "HeaderLiveTV": "التلفاز المباشر", "HeaderNextUp": "التالي", "HeaderRecordingGroups": "مجموعات التسجيل", - "HomeVideos": "الفيديوهات المنزلية", + "HomeVideos": "الفيديوهات الشخصية", "Inherit": "توريث", "ItemAddedWithName": "تم إضافة {0} للمكتبة", "ItemRemovedWithName": "تم إزالة {0} من المكتبة", From b830d38a347fd045e0a8573e69180dd5d296fdd9 Mon Sep 17 00:00:00 2001 From: Roel van Uden <> Date: Tue, 26 Oct 2021 12:59:11 +0200 Subject: [PATCH 226/278] Rework subtitle selection to reduce code clutter --- .../Library/MediaStreamSelector.cs | 49 ++++++------------- 1 file changed, 15 insertions(+), 34 deletions(-) diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index 71e3404d07e..f94eeda8fc3 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -38,14 +38,11 @@ namespace Emby.Server.Implementations.Library } public static int? GetDefaultSubtitleStreamIndex( - List streams, + IEnumerable streams, string[] preferredLanguages, SubtitlePlaybackMode mode, string audioTrackLanguage) { - streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages) - .ToList(); - MediaStream stream = null; if (mode == SubtitlePlaybackMode.None) @@ -53,62 +50,46 @@ namespace Emby.Server.Implementations.Library return null; } + var sortedStreams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages) + .OrderByDescending(x => x.IsExternal) + .ThenByDescending(x => x.IsForced && string.Equals(x.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) + .ThenByDescending(x => x.IsForced) + .ThenByDescending(x => x.IsDefault) + .ToList(); + if (mode == SubtitlePlaybackMode.Default) { // Prefer embedded metadata over smart logic - - stream = streams.FirstOrDefault(s => s.IsExternal && s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ?? - streams.FirstOrDefault(s => s.IsExternal && s.IsForced) ?? - streams.FirstOrDefault(s => s.IsExternal && s.IsDefault) ?? - streams.FirstOrDefault(s => s.IsExternal) ?? - streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ?? - streams.FirstOrDefault(s => s.IsForced) ?? - streams.FirstOrDefault(s => s.IsDefault); + stream = sortedStreams.FirstOrDefault(s => s.IsExternal || s.IsForced || s.IsDefault); // if the audio language is not understood by the user, load their preferred subs, if there are any if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase)) { - stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)); + stream = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)); } } else if (mode == SubtitlePlaybackMode.Smart) { - // Prefer smart logic over embedded metadata - // if the audio language is not understood by the user, load their preferred subs, if there are any if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase)) { - stream = streams.FirstOrDefault(s => s.IsExternal && !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ?? - streams.FirstOrDefault(s => s.IsExternal && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ?? - streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ?? - streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)); + stream = sortedStreams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)); } } else if (mode == SubtitlePlaybackMode.Always) { // always load the most suitable full subtitles - stream = streams.FirstOrDefault(s => s.IsExternal && !s.IsForced) ?? - streams.FirstOrDefault(s => !s.IsForced); + stream = sortedStreams.FirstOrDefault(s => !s.IsForced); } else if (mode == SubtitlePlaybackMode.OnlyForced) { // always load the most suitable full subtitles - stream = streams.FirstOrDefault(s => s.IsExternal && s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ?? - streams.FirstOrDefault(s => s.IsExternal && s.IsForced) ?? - streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ?? - streams.FirstOrDefault(s => s.IsForced); + stream = sortedStreams.FirstOrDefault(x => x.IsForced); } // load forced subs if we have found no suitable full subtitles - stream ??= streams.FirstOrDefault(s => s.IsExternal && s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ?? - streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)); - - if (stream != null) - { - return stream.Index; - } - - return null; + stream ??= sortedStreams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)); + return stream?.Index; } private static IEnumerable GetSortedStreams(IEnumerable streams, MediaStreamType type, string[] languagePreferences) From 1b6eb2ff2d2cc3973fa529c721cf50e3ad849646 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 26 Oct 2021 13:56:30 +0200 Subject: [PATCH 227/278] Enable nullable for more files --- .../Channels/ChannelManager.cs | 4 +--- .../Users/UserManager.cs | 6 +----- MediaBrowser.Model/Channels/ChannelFeatures.cs | 8 +++++--- MediaBrowser.Model/Channels/ChannelQuery.cs | 5 ++--- .../Configuration/BaseApplicationConfiguration.cs | 7 +++---- MediaBrowser.Model/Configuration/LibraryOptions.cs | 13 ++++++------- .../Configuration/UserConfiguration.cs | 5 ++--- .../Configuration/XbmcMetadataOptions.cs | 3 +-- MediaBrowser.Model/Dlna/DeviceIdentification.cs | 1 - MediaBrowser.Model/Dlna/ITranscoderSupport.cs | 1 - MediaBrowser.Model/Users/PinRedeemResult.cs | 5 +++-- 11 files changed, 24 insertions(+), 34 deletions(-) diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 178f30de059..09aee602afb 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -586,7 +586,7 @@ namespace Emby.Server.Implementations.Channels { var supportsLatest = provider is ISupportsLatestMedia; - return new ChannelFeatures + return new ChannelFeatures(channel.Name, channel.Id) { CanFilter = !features.MaxPageSize.HasValue, CanSearch = provider is ISearchableChannel, @@ -596,8 +596,6 @@ namespace Emby.Server.Implementations.Channels MediaTypes = features.MediaTypes.ToArray(), SupportsSortOrderToggle = features.SupportsSortOrderToggle, SupportsLatestMedia = supportsLatest, - Name = channel.Name, - Id = channel.Id.ToString("N", CultureInfo.InvariantCulture), SupportsContentDownloading = features.SupportsContentDownloading, AutoRefreshLevels = features.AutoRefreshLevels }; diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 704a6a84ef2..8ca6e8d21bd 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -530,11 +530,7 @@ namespace Jellyfin.Server.Implementations.Users } } - return new PinRedeemResult - { - Success = false, - UsersReset = Array.Empty() - }; + return new PinRedeemResult(); } /// diff --git a/MediaBrowser.Model/Channels/ChannelFeatures.cs b/MediaBrowser.Model/Channels/ChannelFeatures.cs index d925b78b6db..1ca8e80a6f6 100644 --- a/MediaBrowser.Model/Channels/ChannelFeatures.cs +++ b/MediaBrowser.Model/Channels/ChannelFeatures.cs @@ -1,4 +1,3 @@ -#nullable disable #pragma warning disable CS1591 using System; @@ -7,11 +6,14 @@ namespace MediaBrowser.Model.Channels { public class ChannelFeatures { - public ChannelFeatures() + public ChannelFeatures(string name, Guid id) { MediaTypes = Array.Empty(); ContentTypes = Array.Empty(); DefaultSortFields = Array.Empty(); + + Name = name; + Id = id; } /// @@ -24,7 +26,7 @@ namespace MediaBrowser.Model.Channels /// Gets or sets the identifier. /// /// The identifier. - public string Id { get; set; } + public Guid Id { get; set; } /// /// Gets or sets a value indicating whether this instance can search. diff --git a/MediaBrowser.Model/Channels/ChannelQuery.cs b/MediaBrowser.Model/Channels/ChannelQuery.cs index 59966127fbe..f9380ce3a31 100644 --- a/MediaBrowser.Model/Channels/ChannelQuery.cs +++ b/MediaBrowser.Model/Channels/ChannelQuery.cs @@ -1,4 +1,3 @@ -#nullable disable #pragma warning disable CS1591 using System; @@ -13,13 +12,13 @@ namespace MediaBrowser.Model.Channels /// Gets or sets the fields to return within the items, in addition to basic information. /// /// The fields. - public ItemFields[] Fields { get; set; } + public ItemFields[]? Fields { get; set; } public bool? EnableImages { get; set; } public int? ImageTypeLimit { get; set; } - public ImageType[] EnableImageTypes { get; set; } + public ImageType[]? EnableImageTypes { get; set; } /// /// Gets or sets the user identifier. diff --git a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs index b00d2fffb7d..57759a7d391 100644 --- a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs +++ b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs @@ -1,4 +1,3 @@ -#nullable disable using System; using System.Xml.Serialization; @@ -35,21 +34,21 @@ namespace MediaBrowser.Model.Configuration /// Gets or sets the cache path. /// /// The cache path. - public string CachePath { get; set; } + public string? CachePath { get; set; } /// /// Gets or sets the last known version that was ran using the configuration. /// /// The version from previous run. [XmlIgnore] - public Version PreviousVersion { get; set; } + public Version? PreviousVersion { get; set; } /// /// Gets or sets the stringified PreviousVersion to be stored/loaded, /// because System.Version itself isn't xml-serializable. /// /// String value of PreviousVersion. - public string PreviousVersionStr + public string? PreviousVersionStr { get => PreviousVersion?.ToString(); set diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 24698360ec5..aae5359b1db 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -1,4 +1,3 @@ -#nullable disable #pragma warning disable CS1591 using System; @@ -52,21 +51,21 @@ namespace MediaBrowser.Model.Configuration /// Gets or sets the preferred metadata language. /// /// The preferred metadata language. - public string PreferredMetadataLanguage { get; set; } + public string? PreferredMetadataLanguage { get; set; } /// /// Gets or sets the metadata country code. /// /// The metadata country code. - public string MetadataCountryCode { get; set; } + public string? MetadataCountryCode { get; set; } public string SeasonZeroDisplayName { get; set; } - public string[] MetadataSavers { get; set; } + public string[]? MetadataSavers { get; set; } public string[] DisabledLocalMetadataReaders { get; set; } - public string[] LocalMetadataReaderOrder { get; set; } + public string[]? LocalMetadataReaderOrder { get; set; } public string[] DisabledSubtitleFetchers { get; set; } @@ -76,7 +75,7 @@ namespace MediaBrowser.Model.Configuration public bool SkipSubtitlesIfAudioTrackMatches { get; set; } - public string[] SubtitleDownloadLanguages { get; set; } + public string[]? SubtitleDownloadLanguages { get; set; } public bool RequirePerfectSubtitleMatch { get; set; } @@ -84,7 +83,7 @@ namespace MediaBrowser.Model.Configuration public TypeOptions[] TypeOptions { get; set; } - public TypeOptions GetTypeOptions(string type) + public TypeOptions? GetTypeOptions(string type) { foreach (var options in TypeOptions) { diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index 935e6cbe100..81359462c3d 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -1,4 +1,3 @@ -#nullable disable #pragma warning disable CS1591 using System; @@ -33,7 +32,7 @@ namespace MediaBrowser.Model.Configuration /// Gets or sets the audio language preference. /// /// The audio language preference. - public string AudioLanguagePreference { get; set; } + public string? AudioLanguagePreference { get; set; } /// /// Gets or sets a value indicating whether [play default audio track]. @@ -45,7 +44,7 @@ namespace MediaBrowser.Model.Configuration /// Gets or sets the subtitle language preference. /// /// The subtitle language preference. - public string SubtitleLanguagePreference { get; set; } + public string? SubtitleLanguagePreference { get; set; } public bool DisplayMissingEpisodes { get; set; } diff --git a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs index 8ad070dcbff..07129d71507 100644 --- a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs +++ b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs @@ -1,4 +1,3 @@ -#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Configuration @@ -13,7 +12,7 @@ namespace MediaBrowser.Model.Configuration EnablePathSubstitution = true; } - public string UserId { get; set; } + public string? UserId { get; set; } public string ReleaseDateFormat { get; set; } diff --git a/MediaBrowser.Model/Dlna/DeviceIdentification.cs b/MediaBrowser.Model/Dlna/DeviceIdentification.cs index c511801f4f3..6625b798140 100644 --- a/MediaBrowser.Model/Dlna/DeviceIdentification.cs +++ b/MediaBrowser.Model/Dlna/DeviceIdentification.cs @@ -1,4 +1,3 @@ -#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dlna/ITranscoderSupport.cs b/MediaBrowser.Model/Dlna/ITranscoderSupport.cs index d9bd094d9ff..a70ce44cc65 100644 --- a/MediaBrowser.Model/Dlna/ITranscoderSupport.cs +++ b/MediaBrowser.Model/Dlna/ITranscoderSupport.cs @@ -1,4 +1,3 @@ -#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Dlna diff --git a/MediaBrowser.Model/Users/PinRedeemResult.cs b/MediaBrowser.Model/Users/PinRedeemResult.cs index 7e4553bac85..23fa631e86e 100644 --- a/MediaBrowser.Model/Users/PinRedeemResult.cs +++ b/MediaBrowser.Model/Users/PinRedeemResult.cs @@ -1,6 +1,7 @@ -#nullable disable #pragma warning disable CS1591 +using System; + namespace MediaBrowser.Model.Users { public class PinRedeemResult @@ -15,6 +16,6 @@ namespace MediaBrowser.Model.Users /// Gets or sets the users reset. /// /// The users reset. - public string[] UsersReset { get; set; } + public string[] UsersReset { get; set; } = Array.Empty(); } } From 39d5bdac96b17eb92bd304736cc2728832e1cad0 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Oct 2021 14:47:34 +0200 Subject: [PATCH 228/278] Change ReadOnlySpan to string following PR 6383 (#6734) --- Emby.Naming/Video/CleanStringParser.cs | 24 +++++++------------ Emby.Naming/Video/VideoResolver.cs | 6 ++--- .../Video/CleanStringTests.cs | 10 ++++---- 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs index 99cb289a25a..b8133350087 100644 --- a/Emby.Naming/Video/CleanStringParser.cs +++ b/Emby.Naming/Video/CleanStringParser.cs @@ -17,11 +17,11 @@ namespace Emby.Naming.Video /// List of regex to parse name and year from. /// Parsing result string. /// True if parsing was successful. - public static bool TryClean([NotNullWhen(true)] string? name, IReadOnlyList expressions, out ReadOnlySpan newName) + public static bool TryClean([NotNullWhen(true)] string? name, IReadOnlyList expressions, out string newName) { if (string.IsNullOrEmpty(name)) { - newName = ReadOnlySpan.Empty; + newName = string.Empty; return false; } @@ -32,32 +32,24 @@ namespace Emby.Naming.Video if (TryClean(name, expressions[i], out newName)) { cleaned = true; - name = newName.ToString(); + name = newName; } } - newName = cleaned ? name.AsSpan() : ReadOnlySpan.Empty; + newName = cleaned ? name : string.Empty; return cleaned; } - private static bool TryClean(string name, Regex expression, out ReadOnlySpan newName) + private static bool TryClean(string name, Regex expression, out string newName) { var match = expression.Match(name); - int index = match.Index; - if (match.Success) + if (match.Success && match.Groups.TryGetValue("cleaned", out var cleaned)) { - var found = match.Groups.TryGetValue("cleaned", out var cleaned); - if (!found || cleaned == null) - { - newName = ReadOnlySpan.Empty; - return false; - } - - newName = name.AsSpan().Slice(cleaned.Index, cleaned.Length); + newName = cleaned.Value; return true; } - newName = ReadOnlySpan.Empty; + newName = string.Empty; return false; } } diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index 3b1d906c647..4c9df27f504 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -87,9 +87,9 @@ namespace Emby.Naming.Video year = cleanDateTimeResult.Year; if (extraResult.ExtraType == null - && TryCleanString(name, namingOptions, out ReadOnlySpan newName)) + && TryCleanString(name, namingOptions, out var newName)) { - name = newName.ToString(); + name = newName; } } @@ -138,7 +138,7 @@ namespace Emby.Naming.Video /// The naming options. /// Clean name. /// True if cleaning of name was successful. - public static bool TryCleanString([NotNullWhen(true)] string? name, NamingOptions namingOptions, out ReadOnlySpan newName) + public static bool TryCleanString([NotNullWhen(true)] string? name, NamingOptions namingOptions, out string newName) { return CleanStringParser.TryClean(name, namingOptions.CleanStringRegexes, out newName); } diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs index 1d51e7ca591..1574bce5816 100644 --- a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs @@ -1,4 +1,3 @@ -using System; using Emby.Naming.Common; using Emby.Naming.Video; using Xunit; @@ -32,9 +31,8 @@ namespace Jellyfin.Naming.Tests.Video // FIXME: [InlineData("After The Sunset - [0004].mkv", "After The Sunset")] public void CleanStringTest_NeedsCleaning_Success(string input, string expectedName) { - Assert.True(VideoResolver.TryCleanString(input, _namingOptions, out ReadOnlySpan newName)); - // TODO: compare spans when XUnit supports it - Assert.Equal(expectedName, newName.ToString()); + Assert.True(VideoResolver.TryCleanString(input, _namingOptions, out var newName)); + Assert.Equal(expectedName, newName); } [Theory] @@ -47,8 +45,8 @@ namespace Jellyfin.Naming.Tests.Video [InlineData("Run lola run (lola rennt) (2009).mp4")] public void CleanStringTest_DoesntNeedCleaning_False(string? input) { - Assert.False(VideoResolver.TryCleanString(input, _namingOptions, out ReadOnlySpan newName)); - Assert.True(newName.IsEmpty); + Assert.False(VideoResolver.TryCleanString(input, _namingOptions, out var newName)); + Assert.True(string.IsNullOrEmpty(newName)); } } } From f5ca9cbc3bcffc13830669d5790525c6c11f9136 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 26 Oct 2021 15:49:01 +0200 Subject: [PATCH 229/278] Enable nullable for MediaBrowser.Providers --- .../Providers/IExternalId.cs | 4 +--- .../Providers/ExternalIdInfo.cs | 4 ++-- MediaBrowser.Providers/Manager/ImageSaver.cs | 2 ++ .../Manager/ItemImageProvider.cs | 2 ++ .../Manager/MetadataService.cs | 2 ++ .../Manager/ProviderManager.cs | 2 ++ .../Manager/ProviderUtils.cs | 2 ++ .../Manager/RefreshResult.cs | 2 +- .../MediaBrowser.Providers.csproj | 1 - .../MediaInfo/AudioImageProvider.cs | 2 ++ .../MediaInfo/EmbeddedImageProvider.cs | 2 ++ .../MediaInfo/FFProbeAudioInfo.cs | 2 ++ .../MediaInfo/FFProbeProvider.cs | 2 ++ .../MediaInfo/FFProbeVideoInfo.cs | 2 ++ .../MediaInfo/SubtitleDownloader.cs | 2 ++ .../MediaInfo/SubtitleResolver.cs | 2 ++ .../MediaInfo/SubtitleScheduledTask.cs | 2 ++ .../Movies/ImdbExternalId.cs | 2 +- .../Movies/ImdbPersonExternalId.cs | 2 +- .../Music/AlbumInfoExtensions.cs | 12 ++++++------ .../Music/AlbumMetadataService.cs | 4 ++-- MediaBrowser.Providers/Music/ImvdbId.cs | 2 +- .../Playlists/PlaylistItemsProvider.cs | 2 ++ .../Plugins/AudioDb/AudioDbAlbumExternalId.cs | 2 +- .../AudioDb/AudioDbAlbumImageProvider.cs | 2 ++ .../Plugins/AudioDb/AudioDbAlbumProvider.cs | 2 ++ .../AudioDb/AudioDbArtistExternalId.cs | 2 +- .../AudioDb/AudioDbArtistImageProvider.cs | 2 ++ .../Plugins/AudioDb/AudioDbArtistProvider.cs | 2 ++ .../AudioDb/AudioDbOtherAlbumExternalId.cs | 2 +- .../AudioDb/AudioDbOtherArtistExternalId.cs | 2 +- .../Configuration/PluginConfiguration.cs | 2 +- .../Plugins/AudioDb/Plugin.cs | 1 + .../Configuration/PluginConfiguration.cs | 19 ++++--------------- .../MusicBrainzAlbumArtistExternalId.cs | 2 +- .../MusicBrainz/MusicBrainzAlbumExternalId.cs | 2 +- .../MusicBrainz/MusicBrainzAlbumProvider.cs | 2 ++ .../MusicBrainzArtistExternalId.cs | 2 +- .../MusicBrainz/MusicBrainzArtistProvider.cs | 2 ++ .../MusicBrainzOtherArtistExternalId.cs | 2 +- .../MusicBrainzReleaseGroupExternalId.cs | 2 +- .../Plugins/MusicBrainz/MusicBrainzTrackId.cs | 2 +- .../Plugins/MusicBrainz/Plugin.cs | 1 + .../Omdb/Configuration/PluginConfiguration.cs | 2 +- .../JsonOmdbNotAvailableInt32Converter.cs | 2 -- .../JsonOmdbNotAvailableStringConverter.cs | 2 -- .../Plugins/Omdb/OmdbEpisodeProvider.cs | 2 +- .../Plugins/Omdb/OmdbImageProvider.cs | 2 ++ .../Plugins/Omdb/OmdbItemProvider.cs | 2 ++ .../Plugins/Omdb/OmdbProvider.cs | 2 ++ MediaBrowser.Providers/Plugins/Omdb/Plugin.cs | 1 + .../Tmdb/BoxSets/TmdbBoxSetExternalId.cs | 2 +- .../Tmdb/BoxSets/TmdbBoxSetImageProvider.cs | 2 ++ .../Tmdb/BoxSets/TmdbBoxSetProvider.cs | 2 ++ .../Tmdb/Movies/TmdbMovieExternalId.cs | 2 +- .../Tmdb/Movies/TmdbMovieImageProvider.cs | 2 ++ .../Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 2 ++ .../Tmdb/People/TmdbPersonExternalId.cs | 2 +- .../Plugins/Tmdb/People/TmdbPersonProvider.cs | 2 ++ MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs | 2 ++ .../Tmdb/TV/TmdbEpisodeImageProvider.cs | 2 ++ .../Plugins/Tmdb/TV/TmdbEpisodeProvider.cs | 2 ++ .../Plugins/Tmdb/TV/TmdbSeasonProvider.cs | 2 +- .../Plugins/Tmdb/TV/TmdbSeriesExternalId.cs | 2 +- .../Plugins/Tmdb/TV/TmdbSeriesProvider.cs | 2 ++ .../Plugins/Tmdb/TmdbClientManager.cs | 4 +++- .../Plugins/Tmdb/TmdbUtils.cs | 2 -- .../Studios/StudiosImageProvider.cs | 2 ++ .../Subtitles/SubtitleManager.cs | 3 ++- .../TV/SeasonMetadataService.cs | 2 +- .../TV/SeriesMetadataService.cs | 2 ++ MediaBrowser.Providers/TV/Zap2ItExternalId.cs | 2 +- 72 files changed, 115 insertions(+), 61 deletions(-) diff --git a/MediaBrowser.Controller/Providers/IExternalId.cs b/MediaBrowser.Controller/Providers/IExternalId.cs index e2dbef2bc1c..0d847520d16 100644 --- a/MediaBrowser.Controller/Providers/IExternalId.cs +++ b/MediaBrowser.Controller/Providers/IExternalId.cs @@ -1,5 +1,3 @@ -#nullable disable - using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; @@ -35,7 +33,7 @@ namespace MediaBrowser.Controller.Providers /// /// Gets the URL format string for this id. /// - string UrlFormatString { get; } + string? UrlFormatString { get; } /// /// Determines whether this id supports a given item type. diff --git a/MediaBrowser.Model/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs index 0ea3e96cae9..d026d574fa7 100644 --- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs +++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Model.Providers /// Key for this id. This key should be unique across all providers. /// Specific media type for this id. /// URL format string. - public ExternalIdInfo(string name, string key, ExternalIdMediaType? type, string urlFormatString) + public ExternalIdInfo(string name, string key, ExternalIdMediaType? type, string? urlFormatString) { Name = name; Key = key; @@ -46,6 +46,6 @@ namespace MediaBrowser.Model.Providers /// /// Gets or sets the URL format string. /// - public string UrlFormatString { get; set; } + public string? UrlFormatString { get; set; } } } diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 3d8dd148607..8ded2d14468 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 3593697d7ed..39372acb94a 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CA1002, CS1591 using System; diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index ab8d3a2a61f..ffb3baeb1ff 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 9be887e9c36..ca557f6d618 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index 6d088e6e707..b90136d5098 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Manager/RefreshResult.cs b/MediaBrowser.Providers/Manager/RefreshResult.cs index 72fc61e424e..663ffc5245e 100644 --- a/MediaBrowser.Providers/Manager/RefreshResult.cs +++ b/MediaBrowser.Providers/Manager/RefreshResult.cs @@ -8,7 +8,7 @@ namespace MediaBrowser.Providers.Manager { public ItemUpdateType UpdateType { get; set; } - public string ErrorMessage { get; set; } + public string? ErrorMessage { get; set; } public int Failures { get; set; } } diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 15badfad7e1..9d0a6944bb7 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -30,7 +30,6 @@ false true ../jellyfin.ruleset - disable diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index 12125cbb95b..8c81b08db7d 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CA1002, CS1591 using System; diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs index 1d9d1e02a54..c41d36d7637 100644 --- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs index cf271e7db18..9eb79c39d84 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 4fff57273c7..d4b5d8655c4 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 261c012a1e2..4ab15f60e24 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CA1068, CS1591 using System; diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs index cba2f1c421d..b2b93940a87 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CA1002, CS1591 using System; diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index d7f6a5fac24..dd4a5f061ca 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CA1002, CS1591 using System; diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 4dfe2c59f70..1eacbf1e1bc 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Movies/ImdbExternalId.cs b/MediaBrowser.Providers/Movies/ImdbExternalId.cs index a8d74aa0b5b..d00f37db584 100644 --- a/MediaBrowser.Providers/Movies/ImdbExternalId.cs +++ b/MediaBrowser.Providers/Movies/ImdbExternalId.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Movies public ExternalIdMediaType? Type => null; /// - public string UrlFormatString => "https://www.imdb.com/title/{0}"; + public string? UrlFormatString => "https://www.imdb.com/title/{0}"; /// public bool Supports(IHasProviderIds item) diff --git a/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs b/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs index 8151ab4715c..1bb5e1ea830 100644 --- a/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs +++ b/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Movies public ExternalIdMediaType? Type => ExternalIdMediaType.Person; /// - public string UrlFormatString => "https://www.imdb.com/name/{0}"; + public string? UrlFormatString => "https://www.imdb.com/name/{0}"; /// public bool Supports(IHasProviderIds item) => item is Person; diff --git a/MediaBrowser.Providers/Music/AlbumInfoExtensions.cs b/MediaBrowser.Providers/Music/AlbumInfoExtensions.cs index dddfd02e4f0..d3fce37c715 100644 --- a/MediaBrowser.Providers/Music/AlbumInfoExtensions.cs +++ b/MediaBrowser.Providers/Music/AlbumInfoExtensions.cs @@ -8,7 +8,7 @@ namespace MediaBrowser.Providers.Music { public static class AlbumInfoExtensions { - public static string GetAlbumArtist(this AlbumInfo info) + public static string? GetAlbumArtist(this AlbumInfo info) { var id = info.SongInfos.SelectMany(i => i.AlbumArtists) .FirstOrDefault(i => !string.IsNullOrEmpty(i)); @@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.Music return info.AlbumArtists.Count > 0 ? info.AlbumArtists[0] : default; } - public static string GetReleaseGroupId(this AlbumInfo info) + public static string? GetReleaseGroupId(this AlbumInfo info) { var id = info.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup); @@ -34,7 +34,7 @@ namespace MediaBrowser.Providers.Music return id; } - public static string GetReleaseId(this AlbumInfo info) + public static string? GetReleaseId(this AlbumInfo info) { var id = info.GetProviderId(MetadataProvider.MusicBrainzAlbum); @@ -47,9 +47,9 @@ namespace MediaBrowser.Providers.Music return id; } - public static string GetMusicBrainzArtistId(this AlbumInfo info) + public static string? GetMusicBrainzArtistId(this AlbumInfo info) { - info.ProviderIds.TryGetValue(MetadataProvider.MusicBrainzAlbumArtist.ToString(), out string id); + info.ProviderIds.TryGetValue(MetadataProvider.MusicBrainzAlbumArtist.ToString(), out string? id); if (string.IsNullOrEmpty(id)) { @@ -65,7 +65,7 @@ namespace MediaBrowser.Providers.Music return id; } - public static string GetMusicBrainzArtistId(this ArtistInfo info) + public static string? GetMusicBrainzArtistId(this ArtistInfo info) { info.ProviderIds.TryGetValue(MetadataProvider.MusicBrainzArtist.ToString(), out var id); diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs index 8c9a1f59b12..7c5b80e1e4f 100644 --- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs +++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs @@ -81,7 +81,7 @@ namespace MediaBrowser.Providers.Music if (!item.AlbumArtists.SequenceEqual(artists, StringComparer.OrdinalIgnoreCase)) { item.AlbumArtists = artists; - updateType = updateType | ItemUpdateType.MetadataEdit; + updateType |= ItemUpdateType.MetadataEdit; } return updateType; @@ -100,7 +100,7 @@ namespace MediaBrowser.Providers.Music if (!item.Artists.SequenceEqual(artists, StringComparer.OrdinalIgnoreCase)) { item.Artists = artists; - updateType = updateType | ItemUpdateType.MetadataEdit; + updateType |= ItemUpdateType.MetadataEdit; } return updateType; diff --git a/MediaBrowser.Providers/Music/ImvdbId.cs b/MediaBrowser.Providers/Music/ImvdbId.cs index a1726b99658..ed69f369c07 100644 --- a/MediaBrowser.Providers/Music/ImvdbId.cs +++ b/MediaBrowser.Providers/Music/ImvdbId.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Music public ExternalIdMediaType? Type => null; /// - public string UrlFormatString => null; + public string? UrlFormatString => null; /// public bool Supports(IHasProviderIds item) diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs index 067d585cba1..8baca30c657 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs index 138cfef19a7..3a400575bce 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public ExternalIdMediaType? Type => null; /// - public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; + public string? UrlFormatString => "https://www.theaudiodb.com/album/{0}"; /// public bool Supports(IHasProviderIds item) => item is MusicAlbum; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs index 81bbc26b82a..ad0247fb2ed 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs index ebdcb94eb6c..43f30824b80 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CA1002, CS1591, SA1300 using System; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs index 8aceb48c0c0..b9e57eb2650 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public ExternalIdMediaType? Type => ExternalIdMediaType.Artist; /// - public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; + public string? UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; /// public bool Supports(IHasProviderIds item) => item is MusicArtist; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs index 3ffdcdbeb2b..9c244766083 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs index 321013b17cd..538dc67c426 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CA1034, CS1591, CA1002, SA1028, SA1300 using System; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs index 014481da246..f8f6253ffd9 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public ExternalIdMediaType? Type => ExternalIdMediaType.Album; /// - public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; + public string? UrlFormatString => "https://www.theaudiodb.com/album/{0}"; /// public bool Supports(IHasProviderIds item) => item is Audio; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs index 78753910432..fd598c91867 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist; /// - public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; + public string? UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; /// public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs index 664474dcddb..d61ec6cb175 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using MediaBrowser.Model.Plugins; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs index ba0d7b5697f..6c2ad0573ec 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs index 0cec9e359ca..9c27bd7d3fc 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using MediaBrowser.Model.Plugins; @@ -12,24 +12,13 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz public string Server { - get - { - return _server; - } - - set - { - _server = value.TrimEnd('/'); - } + get => _server; + set => _server = value.TrimEnd('/'); } public long RateLimit { - get - { - return _rateLimit; - } - + get => _rateLimit; set { if (value < Plugin.DefaultRateLimit && _server == Plugin.DefaultServer) diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs index 1b37e2a60df..c54cdda3d39 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Music public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist; /// - public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; + public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; /// public bool Supports(IHasProviderIds item) => item is Audio; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs index ef095111a8d..8f7fadd0600 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Music public ExternalIdMediaType? Type => ExternalIdMediaType.Album; /// - public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}"; + public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}"; /// public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index 93f8902def4..5559b9db6ab 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591, SA1401 using System; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs index d654e1372fd..941ffea721d 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Music public ExternalIdMediaType? Type => ExternalIdMediaType.Artist; /// - public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; + public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; /// public bool Supports(IHasProviderIds item) => item is MusicArtist; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs index 7cff5f59520..1feb7f4eab9 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs index f889a34b5cd..05db2d98f76 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Music public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist; /// - public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; + public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; /// public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs index 53783d2c0cd..acb652fe011 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Music public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup; /// - public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}"; + public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}"; /// public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs index 627f8f098d0..14805b9b792 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Music public ExternalIdMediaType? Type => ExternalIdMediaType.Track; /// - public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}"; + public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}"; /// public bool Supports(IHasProviderIds item) => item is Audio; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs index 69b69be4289..cfa10dd648c 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs index 196f14e7c8e..0995470055a 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using MediaBrowser.Model.Plugins; diff --git a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs index 19d90b9a1b2..8bfdc461e6f 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.ComponentModel; using System.Text.Json; diff --git a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs index c19589d45bd..f35880a0460 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs index 24ef80a35d6..f67ac6ede49 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs @@ -61,7 +61,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb return result; } - if (info.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out string seriesImdbId) && !string.IsNullOrEmpty(seriesImdbId)) + if (info.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out string? seriesImdbId) && !string.IsNullOrEmpty(seriesImdbId)) { if (info.IndexNumber.HasValue && info.ParentIndexNumber.HasValue) { diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs index df67aff31c2..fa82089c86c 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs index 02e696de519..2409993a293 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591, SA1300 using System; diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index b2bc58eea1a..816a882b46f 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS159, SA1300 using System; diff --git a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs index 047df4f33de..a0fba48f052 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs index 1f7ec643385..3217ac2f138 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets public ExternalIdMediaType? Type => ExternalIdMediaType.BoxSet; /// - public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "collection/{0}"; + public string? UrlFormatString => TmdbUtils.BaseTmdbUrl + "collection/{0}"; /// public bool Supports(IHasProviderIds item) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs index fc6af0b34e1..35dc36811e7 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs index 5dd1f0b73af..62bc9c65f9c 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs index f1a1b65d82e..31310a8d41f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies public ExternalIdMediaType? Type => ExternalIdMediaType.Movie; /// - public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "movie/{0}"; + public string? UrlFormatString => TmdbUtils.BaseTmdbUrl + "movie/{0}"; /// public bool Supports(IHasProviderIds item) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs index d3cef49d8ce..015eddc1ac6 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index 54f8d450ac9..9dd0678561f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs index de74a7a4c76..9804d60bddd 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People public ExternalIdMediaType? Type => ExternalIdMediaType.Person; /// - public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "person/{0}"; + public string? UrlFormatString => TmdbUtils.BaseTmdbUrl + "person/{0}"; /// public bool Supports(IHasProviderIds item) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs index dac11838890..8790e375926 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs b/MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs index ea81eb96e09..4adde8366a9 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using MediaBrowser.Common.Configuration; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs index 45e18c0ac09..eb75e940502 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index 8ec8f64641e..3f826843a69 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs index 66e30115dc5..4ac88968014 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs @@ -33,7 +33,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV { var result = new MetadataResult(); - info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string seriesTmdbId); + info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string? seriesTmdbId); var seasonNumber = info.IndexNumber; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs index 6ecc055d71d..8a2be80cde5 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV public ExternalIdMediaType? Type => ExternalIdMediaType.Series; /// - public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "tv/{0}"; + public string? UrlFormatString => TmdbUtils.BaseTmdbUrl + "tv/{0}"; /// public bool Supports(IHasProviderIds item) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index da76345b5b0..feda15cf75d 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index 5bd5dd2e802..74be4c79338 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -1,4 +1,6 @@ -using System; +#nullable disable + +using System; using System.Collections.Generic; using System.Globalization; using System.Threading; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index b713736a0f6..58ab9f54737 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.Text.RegularExpressions; diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs index a1873eaae46..7f1665532aa 100644 --- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 73f9d7b6270..c2b420c3316 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -21,7 +23,6 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; using Microsoft.Extensions.Logging; -using static MediaBrowser.Model.IO.IODefaults; namespace MediaBrowser.Providers.Subtitles { diff --git a/MediaBrowser.Providers/TV/SeasonMetadataService.cs b/MediaBrowser.Providers/TV/SeasonMetadataService.cs index 0f22f8a9b62..b173fc7a3c5 100644 --- a/MediaBrowser.Providers/TV/SeasonMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeasonMetadataService.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.TV if (!string.Equals(item.Name, seasonZeroDisplayName, StringComparison.OrdinalIgnoreCase)) { item.Name = seasonZeroDisplayName; - updatedType = updatedType | ItemUpdateType.MetadataEdit; + updatedType |= ItemUpdateType.MetadataEdit; } } diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index dcb6934086a..9d223b4b669 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Providers/TV/Zap2ItExternalId.cs b/MediaBrowser.Providers/TV/Zap2ItExternalId.cs index 3cb18e4248d..087e4036a5c 100644 --- a/MediaBrowser.Providers/TV/Zap2ItExternalId.cs +++ b/MediaBrowser.Providers/TV/Zap2ItExternalId.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.TV public ExternalIdMediaType? Type => null; /// - public string UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}"; + public string? UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}"; /// public bool Supports(IHasProviderIds item) => item is Series; From 2410b3a3cfeff716740c0c6c15f1896a224771a9 Mon Sep 17 00:00:00 2001 From: Deathspike Date: Tue, 26 Oct 2021 20:51:35 +0200 Subject: [PATCH 230/278] Resolve subtitle selection feedback (#6446) --- Emby.Server.Implementations/Library/MediaStreamSelector.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index f94eeda8fc3..b3837fedb40 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -50,7 +50,8 @@ namespace Emby.Server.Implementations.Library return null; } - var sortedStreams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages) + var sortedStreams = streams + .Where(i => i.Type == MediaStreamType.Subtitle) .OrderByDescending(x => x.IsExternal) .ThenByDescending(x => x.IsForced && string.Equals(x.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) .ThenByDescending(x => x.IsForced) @@ -73,7 +74,8 @@ namespace Emby.Server.Implementations.Library // if the audio language is not understood by the user, load their preferred subs, if there are any if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase)) { - stream = sortedStreams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)); + stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ?? + streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)); } } else if (mode == SubtitlePlaybackMode.Always) From a1b63aaa08ec318df7e3992e4cfc3e9fce5c8b48 Mon Sep 17 00:00:00 2001 From: Alan Azar Date: Mon, 25 Oct 2021 18:31:58 +0000 Subject: [PATCH 231/278] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/ --- Emby.Server.Implementations/Localization/Core/ar.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index ee70aa8d86f..f60d2fd0169 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -33,7 +33,7 @@ "LabelRunningTimeValue": "المدة: {0}", "Latest": "الأحدث", "MessageApplicationUpdated": "لقد تم تحديث خادم Jellyfin", - "MessageApplicationUpdatedTo": "تم تحديث سيرفر Jellyfin الى {0}", + "MessageApplicationUpdatedTo": "تم تحديث خادم Jellyfin الى {0}", "MessageNamedServerConfigurationUpdatedWithValue": "تم تحديث إعدادات الخادم في قسم {0}", "MessageServerConfigurationUpdated": "تم تحديث إعدادات الخادم", "MixedContent": "محتوى مختلط", @@ -43,7 +43,7 @@ "NameInstallFailed": "فشل التثبيت {0}", "NameSeasonNumber": "الموسم {0}", "NameSeasonUnknown": "الموسم غير معروف", - "NewVersionIsAvailable": "نسخة جديدة من سيرفر Jellyfin متوفرة للتحميل.", + "NewVersionIsAvailable": "نسخة جديدة من خادم Jellyfin متوفرة للتحميل.", "NotificationOptionApplicationUpdateAvailable": "يوجد تحديث للتطبيق", "NotificationOptionApplicationUpdateInstalled": "تم تحديث التطبيق", "NotificationOptionAudioPlayback": "بدأ تشغيل المقطع الصوتي", @@ -55,7 +55,7 @@ "NotificationOptionPluginInstalled": "تم تثبيت الملحق", "NotificationOptionPluginUninstalled": "تمت إزالة الملحق", "NotificationOptionPluginUpdateInstalled": "تم تثبيت تحديثات الملحق", - "NotificationOptionServerRestartRequired": "يجب إعادة تشغيل السيرفر", + "NotificationOptionServerRestartRequired": "يجب إعادة تشغيل الخادم", "NotificationOptionTaskFailed": "فشل في المهمة المجدولة", "NotificationOptionUserLockedOut": "تم إقفال حساب المستخدم", "NotificationOptionVideoPlayback": "بدأ تشغيل الفيديو", @@ -72,7 +72,7 @@ "ServerNameNeedsToBeRestarted": "يحتاج لإعادة تشغيله {0}", "Shows": "الحلقات", "Songs": "الأغاني", - "StartupEmbyServerIsLoading": "سيرفر Jellyfin قيد التشغيل . الرجاء المحاولة بعد قليل.", + "StartupEmbyServerIsLoading": "خادم Jellyfin قيد التشغيل . الرجاء المحاولة بعد قليل.", "SubtitleDownloadFailureForItem": "عملية إنزال الترجمة فشلت لـ{0}", "SubtitleDownloadFailureFromForItem": "الترجمات فشلت في التحميل من {0} الى {1}", "Sync": "مزامنة", From 91655bb5fea6c711c66d3527aceee91edd9a4000 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Tue, 26 Oct 2021 11:54:25 +0000 Subject: [PATCH 232/278] Translated using Weblate (Esperanto) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eo/ --- .../Localization/Core/eo.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json index 7ce87502cc6..f92b5f67303 100644 --- a/Emby.Server.Implementations/Localization/Core/eo.json +++ b/Emby.Server.Implementations/Localization/Core/eo.json @@ -7,8 +7,8 @@ "NameInstallFailed": "{0} instalado fiaskis", "Music": "Muziko", "Movies": "Filmoj", - "ItemRemovedWithName": "{0} forigis el la libraro", - "ItemAddedWithName": "{0} aldonis al la libraro", + "ItemRemovedWithName": "{0} forigis el la plurmediteko", + "ItemAddedWithName": "{0} aldonis al la plurmediteko", "HeaderLiveTV": "TV-etero", "HeaderContinueWatching": "Daŭrigi Spektadon", "HeaderAlbumArtists": "Albumo de artisto", @@ -23,7 +23,7 @@ "Application": "Aplikaĵo", "AppDeviceValues": "Aplikaĵo: {0}, Aparato: {1}", "Albums": "Albumoj", - "TasksLibraryCategory": "Libraro", + "TasksLibraryCategory": "Plurmediteko", "VersionNumber": "Versio {0}", "UserDownloadingItemWithValues": "{0} elŝutas {1}", "UserCreatedWithName": "Uzanto {0} kreiĝis", @@ -50,7 +50,7 @@ "TvShows": "TV-serioj", "Favorites": "Favoratoj", "TaskCleanLogs": "Purigi Ĵurnalan Katalogon", - "TaskRefreshLibrary": "Skanu Plurmedian Libraron", + "TaskRefreshLibrary": "Skanu Plurmeditekon", "ValueSpecialEpisodeName": "Speciala - {0}", "TaskOptimizeDatabase": "Optimigi datumbazon", "TaskRefreshChannels": "Refreŝigi Kanalojn", @@ -75,17 +75,17 @@ "ServerNameNeedsToBeRestarted": "{0} devas esti relanĉita", "NotificationOptionVideoPlayback": "La videoludado lanĉis", "NotificationOptionServerRestartRequired": "Servila relanĉigo bezonata", - "TaskOptimizeDatabaseDescription": "Kompaktigas datenbazon kaj trunkas liberan lokon. Lanĉi ĉi tiun taskon post la librara skanado aŭ fari aliajn ŝanĝojn, kiuj implicas datenbazajn modifojn, povus plibonigi rendimenton.", + "TaskOptimizeDatabaseDescription": "Kompaktigas datenbazon kaj trunkas liberan lokon. Lanĉi ĉi tiun taskon post la teka skanado aŭ fari aliajn ŝanĝojn, kiuj implicas datenbazajn modifojn, povus plibonigi rendimenton.", "TaskUpdatePluginsDescription": "Elŝutas kaj instalas ĝisdatigojn por kromprogramojn, kiuj estas agorditaj por ĝisdatigi aŭtomate.", "TaskDownloadMissingSubtitlesDescription": "Serĉas en interreto mankantajn subtekstojn surbaze de metadatena agordaro.", - "TaskRefreshPeopleDescription": "Ĝisdatigas metadatenojn por aktoroj kaj reĵisoroj en via plurmedia libraro.", + "TaskRefreshPeopleDescription": "Ĝisdatigas metadatenojn por aktoroj kaj reĵisoroj en via plurmediteko.", "TaskCleanLogsDescription": "Forigas ĵurnalajn dosierojn aĝajn pli ol {0} tagojn.", - "TaskRefreshLibraryDescription": "Skanas vian plurmedian libraron por novaj dosieroj kaj refreŝigas metadatenaron.", + "TaskRefreshLibraryDescription": "Skanas vian plurmeditekon por novaj dosieroj kaj refreŝigas metadatenaron.", "NewVersionIsAvailable": "Nova versio de Jellyfin Server estas elŝutebla.", "TaskCleanCacheDescription": "Forigas stapla dosierojn ne plu necesajn de la sistemo.", "TaskCleanActivityLogDescription": "Forigas aktivecan ĵurnalaĵojn pli malnovajn ol la agordita aĝo.", "TaskCleanTranscodeDescription": "Forigas transkodajn dosierojn aĝajn pli ol unu tagon.", - "ValueHasBeenAddedToLibrary": "{0} estis aldonita al via plurmedia libraro", + "ValueHasBeenAddedToLibrary": "{0} estis aldonita al via plurmediteko", "SubtitleDownloadFailureFromForItem": "Subtekstoj malsukcesis elŝuti de {0} por {1}", "StartupEmbyServerIsLoading": "Jellyfin Server ŝarĝas. Provi denove baldaŭ.", "TaskRefreshChapterImagesDescription": "Kreas bildetojn por videoj kiuj havas ĉapitrojn.", From 4599ce659c75ffb6dcd21bdbff62cab1eb24c99c Mon Sep 17 00:00:00 2001 From: De sousa John Date: Mon, 25 Oct 2021 07:40:39 +0000 Subject: [PATCH 233/278] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/ --- Emby.Server.Implementations/Localization/Core/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index c3e52eb81a1..1265b6ef583 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -1,7 +1,7 @@ { "Albums": "Albums", "AppDeviceValues": "Application : {0}, Appareil : {1}", - "Application": "Application", + "Application": "Applications", "Artists": "Artistes", "AuthenticationSucceededWithUserName": "{0} authentifié avec succès", "Books": "Livres", From 4885f5e6c9e63452a462884128ca0e9ffb6aeeaf Mon Sep 17 00:00:00 2001 From: zirdum Date: Wed, 27 Oct 2021 10:54:18 -0700 Subject: [PATCH 234/278] Update README.md The link with 'blob' specified in the URL doesn't work for the current version of unRaid. Switching it to 'tree' (which is where the 'blob' link resolves to) works fine. Changes Changed link referenced: https://github.com/jellyfin/jellyfin/blob/master/deployment/unraid/docker-templates To this link: https://github.com/jellyfin/jellyfin/tree/master/deployment/unraid/docker-templates Issues Fixes template showing up in unRaid with the changes applied. --- deployment/unraid/docker-templates/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/unraid/docker-templates/README.md b/deployment/unraid/docker-templates/README.md index 2c268e8b3ef..c4e133db35f 100644 --- a/deployment/unraid/docker-templates/README.md +++ b/deployment/unraid/docker-templates/README.md @@ -8,7 +8,7 @@ Click on the Docker tab Add the following line under "Template Repositories" -https://github.com/jellyfin/jellyfin/blob/master/deployment/unraid/docker-templates +https://github.com/jellyfin/jellyfin/test/master/deployment/unraid/docker-templates Click save than click on Add Container and select jellyfin. From 71ed47a5d3f0e78130bd50bae344406e202ac310 Mon Sep 17 00:00:00 2001 From: zirdum Date: Wed, 27 Oct 2021 10:54:54 -0700 Subject: [PATCH 235/278] Update README.md The link with 'blob' specified in the URL doesn't work for the current version of unRaid. Switching it to 'tree' (which is where the 'blob' link resolves to) works fine. Changes Changed link referenced: https://github.com/jellyfin/jellyfin/blob/master/deployment/unraid/docker-templates To this link: https://github.com/jellyfin/jellyfin/tree/master/deployment/unraid/docker-templates Issues Fixes template showing up in unRaid with the changes applied. --- deployment/unraid/docker-templates/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/unraid/docker-templates/README.md b/deployment/unraid/docker-templates/README.md index c4e133db35f..8e401e0095f 100644 --- a/deployment/unraid/docker-templates/README.md +++ b/deployment/unraid/docker-templates/README.md @@ -8,7 +8,7 @@ Click on the Docker tab Add the following line under "Template Repositories" -https://github.com/jellyfin/jellyfin/test/master/deployment/unraid/docker-templates +https://github.com/jellyfin/jellyfin/tree/master/deployment/unraid/docker-templates Click save than click on Add Container and select jellyfin. From fc2e826b4de1582f2ae2d453bdc6d71262c72d67 Mon Sep 17 00:00:00 2001 From: Alan Azar Date: Wed, 27 Oct 2021 15:21:02 +0000 Subject: [PATCH 236/278] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/ --- Emby.Server.Implementations/Localization/Core/ar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index f60d2fd0169..99220cca97f 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -16,7 +16,7 @@ "Folders": "المجلدات", "Genres": "التضنيفات", "HeaderAlbumArtists": "ألبوم الفنان", - "HeaderContinueWatching": "استئناف", + "HeaderContinueWatching": "استمر بالمشاهدة", "HeaderFavoriteAlbums": "الألبومات المفضلة", "HeaderFavoriteArtists": "الفنانون المفضلون", "HeaderFavoriteEpisodes": "الحلقات المفضلة", From 17973964faed0958190817484bba1603dd8ad51d Mon Sep 17 00:00:00 2001 From: Kenneth SB Date: Wed, 27 Oct 2021 16:49:49 +0000 Subject: [PATCH 237/278] Translated using Weblate (Danish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/da/ --- Emby.Server.Implementations/Localization/Core/da.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index b2c484a31e0..cfe365f57ad 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -15,7 +15,7 @@ "Favorites": "Favoritter", "Folders": "Mapper", "Genres": "Genrer", - "HeaderAlbumArtists": "Albumkunstnere", + "HeaderAlbumArtists": "Kunstnerens album", "HeaderContinueWatching": "Fortsæt Afspilning", "HeaderFavoriteAlbums": "Favoritalbummer", "HeaderFavoriteArtists": "Favoritkunstnere", From 5a7433472ef88c7e8e52840425a7296e242155ee Mon Sep 17 00:00:00 2001 From: Alan Azar Date: Fri, 29 Oct 2021 04:42:23 +0000 Subject: [PATCH 238/278] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/ --- Emby.Server.Implementations/Localization/Core/ar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index 99220cca97f..a83a453b4ca 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -12,7 +12,7 @@ "DeviceOfflineWithName": "قُطِع الاتصال ب{0}", "DeviceOnlineWithName": "{0} متصل", "FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}", - "Favorites": "المفضلة", + "Favorites": "مفضلات", "Folders": "المجلدات", "Genres": "التضنيفات", "HeaderAlbumArtists": "ألبوم الفنان", From 2f6437a987423bf75a32bf7112f596f6cfa242ec Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 31 Oct 2021 08:49:24 -0600 Subject: [PATCH 239/278] Use correct id when finding existing dlna profile --- Emby.Dlna/DlnaManager.cs | 4 ++-- Jellyfin.Api/Controllers/DlnaController.cs | 2 +- MediaBrowser.Controller/Dlna/IDlnaManager.cs | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 73e8a0008ea..f37d2d7d7b6 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -416,7 +416,7 @@ namespace Emby.Dlna } /// - public void UpdateProfile(DeviceProfile profile) + public void UpdateProfile(string profileId, DeviceProfile profile) { profile = ReserializeProfile(profile); @@ -430,7 +430,7 @@ namespace Emby.Dlna throw new ArgumentException("Profile is missing Name"); } - var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profile.Id, StringComparison.OrdinalIgnoreCase)); + var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profileId, StringComparison.OrdinalIgnoreCase)); var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml"; var path = Path.Combine(UserProfilesPath, newFilename); diff --git a/Jellyfin.Api/Controllers/DlnaController.cs b/Jellyfin.Api/Controllers/DlnaController.cs index 052a6aff2e3..35c3a3d922b 100644 --- a/Jellyfin.Api/Controllers/DlnaController.cs +++ b/Jellyfin.Api/Controllers/DlnaController.cs @@ -126,7 +126,7 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - _dlnaManager.UpdateProfile(deviceProfile); + _dlnaManager.UpdateProfile(profileId, deviceProfile); return NoContent(); } } diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs index cc0a107a816..06da5ea0972 100644 --- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs +++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs @@ -37,8 +37,9 @@ namespace MediaBrowser.Controller.Dlna /// /// Updates the profile. /// + /// The profile id. /// The profile. - void UpdateProfile(DeviceProfile profile); + void UpdateProfile(string profileId, DeviceProfile profile); /// /// Deletes the profile. From a22c57ff3323c3e06ffdd828f887ee37933e8ece Mon Sep 17 00:00:00 2001 From: Pedro Almeida Date: Sun, 31 Oct 2021 15:13:23 +0000 Subject: [PATCH 240/278] =?UTF-8?q?Fix=20localization=20typo=20with=20R?= =?UTF-8?q?=C3=A9union?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Emby.Server.Implementations/Localization/countries.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/countries.json b/Emby.Server.Implementations/Localization/countries.json index b08a3ae7980..22ffc5e0970 100644 --- a/Emby.Server.Implementations/Localization/countries.json +++ b/Emby.Server.Implementations/Localization/countries.json @@ -630,7 +630,7 @@ "TwoLetterISORegionName": "MD" }, { - "DisplayName": "Réunion", + "DisplayName": "Réunion", "Name": "RE", "ThreeLetterISORegionName": "REU", "TwoLetterISORegionName": "RE" From 10a173c01164304449787f275df5b70d58b4debd Mon Sep 17 00:00:00 2001 From: Pedro Almeida Date: Sun, 31 Oct 2021 15:28:51 +0000 Subject: [PATCH 241/278] Add pt-pt as culture Makes pt-pt selectable as metadata language --- Emby.Server.Implementations/Localization/iso6392.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/iso6392.txt b/Emby.Server.Implementations/Localization/iso6392.txt index 488901822d6..66fba333045 100644 --- a/Emby.Server.Implementations/Localization/iso6392.txt +++ b/Emby.Server.Implementations/Localization/iso6392.txt @@ -349,7 +349,8 @@ pli||pi|Pali|pali pol||pl|Polish|polonais pon|||Pohnpeian|pohnpei por||pt|Portuguese|portugais -pob||pt-br|Portuguese (Brazil)|portugais +pop||pt-pt|Portuguese (Portugal)|portugais (pt-pt) +pob||pt-br|Portuguese (Brazil)|portugais (pt-br) pra|||Prakrit languages|prâkrit, langues pro|||Provençal, Old (to 1500)|provençal ancien (jusqu'à 1500) pus||ps|Pushto; Pashto|pachto From 7b89e0e3a529295e1193086d9aced67545142ca0 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 31 Oct 2021 11:06:47 -0600 Subject: [PATCH 242/278] Fix tests --- .../Localization/LocalizationManagerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs index 143020d4369..3e7d6ed1dc5 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs @@ -40,7 +40,7 @@ namespace Jellyfin.Server.Implementations.Tests.Localization await localizationManager.LoadAll(); var cultures = localizationManager.GetCultures().ToList(); - Assert.Equal(189, cultures.Count); + Assert.Equal(190, cultures.Count); var germany = cultures.FirstOrDefault(x => x.TwoLetterISOLanguageName.Equals("de", StringComparison.Ordinal)); Assert.NotNull(germany); From 080b02cc4c9879d92de725a763527fb7285cb181 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Sun, 31 Oct 2021 02:40:15 +0200 Subject: [PATCH 243/278] Add comments, minor cleanup, add tests --- .../Manager/ItemImageProvider.cs | 52 +- .../Jellyfin.Providers.Tests.csproj | 6 + .../Manager/ItemImageProviderTests.cs | 674 ++++++++++++++++++ .../Test Data/Images/blank0.jpg | Bin .../Test Data/Images/blank1.jpg | Bin 5 files changed, 720 insertions(+), 12 deletions(-) create mode 100644 tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/Test Data/Images/blank0.jpg create mode 100644 tests/Jellyfin.Providers.Tests/Test Data/Images/blank1.jpg diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 39372acb94a..49b7a5d6b81 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CA1002, CS1591 - using System; using System.Collections.Generic; using System.IO; @@ -25,6 +23,9 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Manager { + /// + /// Utilities for managing images attached to items. + /// public class ItemImageProvider { private readonly ILogger _logger; @@ -47,6 +48,12 @@ namespace MediaBrowser.Providers.Manager ImageType.Thumb }; + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The provider manager for interacting with provider image references. + /// The filesystem. public ItemImageProvider(ILogger logger, IProviderManager providerManager, IFileSystem fileSystem) { _logger = logger; @@ -54,6 +61,13 @@ namespace MediaBrowser.Providers.Manager _fileSystem = fileSystem; } + /// + /// Verifies existing images have valid paths and adds any new local images provided. + /// + /// The to validate images for. + /// The providers to use, must include (s) for local scanning. + /// The directory service for s to use. + /// true if changes were made to the item; otherwise false. public bool ValidateImages(BaseItem item, IEnumerable providers, IDirectoryService directoryService) { var hasChanges = false; @@ -73,6 +87,15 @@ namespace MediaBrowser.Providers.Manager return hasChanges; } + /// + /// Refreshes from the providers according to the given options. + /// + /// The to gather images for. + /// The library options. + /// The providers to query for images. + /// The refresh options. + /// The cancellation token. + /// The refresh result. public async Task RefreshImages( BaseItem item, LibraryOptions libraryOptions, @@ -118,7 +141,7 @@ namespace MediaBrowser.Providers.Manager } /// - /// Refreshes from provider. + /// Refreshes from a dynamic provider. /// private async Task RefreshFromProvider( BaseItem item, @@ -234,7 +257,7 @@ namespace MediaBrowser.Providers.Manager } /// - /// Refreshes from provider. + /// Refreshes from a remote provider. /// /// The item. /// The provider. @@ -305,12 +328,12 @@ namespace MediaBrowser.Providers.Manager } minWidth = savedOptions.GetMinWidth(ImageType.Backdrop); - await DownloadBackdrops(item, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); + await DownloadMultiImages(item, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); - if (item is IHasScreenshots hasScreenshots) + if (item is IHasScreenshots) { minWidth = savedOptions.GetMinWidth(ImageType.Screenshot); - await DownloadBackdrops(item, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); + await DownloadMultiImages(item, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException) @@ -360,6 +383,12 @@ namespace MediaBrowser.Providers.Manager } } + /// + /// Merges a list of images into the provided item, validating existing images and replacing them or adding new images as necessary. + /// + /// The to modify. + /// The new images to place in item. + /// true if changes were made to the item; otherwise false. public bool MergeImages(BaseItem item, IReadOnlyList images) { var changed = false; @@ -417,8 +446,7 @@ namespace MediaBrowser.Providers.Manager changed = true; } - var hasScreenshots = item as IHasScreenshots; - if (hasScreenshots != null) + if (item is IHasScreenshots) { if (UpdateMultiImages(item, images, ImageType.Screenshot)) { @@ -536,7 +564,7 @@ namespace MediaBrowser.Providers.Manager return true; } - if (item is IItemByName && item is not MusicArtist) + if (item is IItemByName and not MusicArtist) { var hasDualAccess = item as IHasDualAccess; if (hasDualAccess == null || hasDualAccess.IsAccessedByName) @@ -569,7 +597,7 @@ namespace MediaBrowser.Providers.Manager newIndex); } - private async Task DownloadBackdrops(BaseItem item, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable images, int minWidth, CancellationToken cancellationToken) + private async Task DownloadMultiImages(BaseItem item, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable images, int minWidth, CancellationToken cancellationToken) { foreach (var image in images.Where(i => i.Type == imageType)) { @@ -609,7 +637,7 @@ namespace MediaBrowser.Providers.Manager break; } - // If there's already an image of the same size, skip it + // If there's already an image of the same file size, skip it if (response.Content.Headers.ContentLength.HasValue) { try diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj index 0b2db64b0bb..9fb1a4364e4 100644 --- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj +++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj @@ -6,6 +6,12 @@ ../jellyfin-tests.ruleset + + + PreserveNewest + + + diff --git a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs new file mode 100644 index 00000000000..253bcb7cadc --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs @@ -0,0 +1,674 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Manager; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Jellyfin.Providers.Tests.Manager +{ + public class ItemImageProviderTests + { + private static readonly string TestDataImagePath = "Test Data/Images/blank{0}.jpg"; + + [Fact] + public void ValidateImages_PhotoEmptyProviders_NoChange() + { + var itemImageProvider = GetItemImageProvider(null, null); + var changed = itemImageProvider.ValidateImages(new Photo(), new List(), null); + + Assert.False(changed); + } + + [Fact] + public void ValidateImages_EmptyItemEmptyProviders_NoChange() + { + var itemImageProvider = GetItemImageProvider(null, null); + var changed = itemImageProvider.ValidateImages(new MovieWithScreenshots(), new List(), null); + + Assert.False(changed); + } + + private static TheoryData GetImageTypesWithCount() + { + var theoryTypes = new TheoryData(); + + // shotgun approach; overkill for frequent runs + // foreach (var imageType in (ImageType[])Enum.GetValues(typeof(ImageType))) + // { + // switch (imageType) + // { + // case ImageType.Chapter: + // case ImageType.Profile: + // // skip types that can't be set using BaseItem.SetImagePath or otherwise don't apply to BaseItem + // break; + // case ImageType.Backdrop: + // case ImageType.Screenshot: + // // for types that support multiple test with 1 and with more than 1 + // theoryTypes.Add(imageType, 1); + // theoryTypes.Add(imageType, 2); + // break; + // default: + // // for singular types just test with 1 + // theoryTypes.Add(imageType, 1); + // break; + // } + // } + + // specific test cases that hit different handling + theoryTypes.Add(ImageType.Primary, 1); + theoryTypes.Add(ImageType.Backdrop, 1); + theoryTypes.Add(ImageType.Backdrop, 2); + theoryTypes.Add(ImageType.Screenshot, 1); + + return theoryTypes; + } + + [Theory] + [MemberData(nameof(GetImageTypesWithCount))] + public void ValidateImages_EmptyItemAndPopulatedProviders_AddsImages(ImageType imageType, int imageCount) + { + // Has to exist for querying DateModified time on file, results stored but not checked so not populating + BaseItem.FileSystem = Mock.Of(); + + var item = new MovieWithScreenshots(); + var imageProvider = GetImageProvider(imageType, imageCount, true); + + var itemImageProvider = GetItemImageProvider(null, null); + var changed = itemImageProvider.ValidateImages(item, new List { imageProvider }, null); + + Assert.True(changed); + Assert.Equal(imageCount, item.GetImages(imageType).Count()); + } + + [Theory] + [MemberData(nameof(GetImageTypesWithCount))] + public void ValidateImages_PopulatedItemWithGoodPathsAndEmptyProviders_NoChange(ImageType imageType, int imageCount) + { + var item = GetItemWithImages(imageType, imageCount, true); + + var itemImageProvider = GetItemImageProvider(null, null); + var changed = itemImageProvider.ValidateImages(item, new List(), null); + + Assert.False(changed); + Assert.Equal(imageCount, item.GetImages(imageType).Count()); + } + + [Theory] + [MemberData(nameof(GetImageTypesWithCount))] + public void ValidateImages_PopulatedItemWithBadPathsAndEmptyProviders_RemovesImage(ImageType imageType, int imageCount) + { + var item = GetItemWithImages(imageType, imageCount, false); + + var itemImageProvider = GetItemImageProvider(null, null); + var changed = itemImageProvider.ValidateImages(item, new List(), null); + + Assert.True(changed); + Assert.Empty(item.GetImages(imageType)); + } + + [Fact] + public void MergeImages_EmptyItemNewImagesEmpty_NoChange() + { + var itemImageProvider = GetItemImageProvider(null, null); + var changed = itemImageProvider.MergeImages(new MovieWithScreenshots(), new List()); + + Assert.False(changed); + } + + [Theory] + [MemberData(nameof(GetImageTypesWithCount))] + public void MergeImages_PopulatedItemWithGoodPathsAndPopulatedNewImages_AddsUpdatesImages(ImageType imageType, int imageCount) + { + // valid and not valid paths - should replace the valid paths with the invalid ones + var item = GetItemWithImages(imageType, imageCount, true); + var images = GetImages(imageType, imageCount, false); + + var itemImageProvider = GetItemImageProvider(null, null); + var changed = itemImageProvider.MergeImages(item, images); + + Assert.True(changed); + // adds for types that allow multiple, replaces singular type images + if (item.AllowsMultipleImages(imageType)) + { + Assert.Equal(imageCount * 2, item.GetImages(imageType).Count()); + } + else + { + Assert.Single(item.GetImages(imageType)); + Assert.Same(images[0].FileInfo.FullName, item.GetImages(imageType).First().Path); + } + } + + [Theory] + [MemberData(nameof(GetImageTypesWithCount))] + public void MergeImages_PopulatedItemWithGoodPathsAndSameNewImages_NoChange(ImageType imageType, int imageCount) + { + var oldTime = new DateTime(1970, 1, 1); + + // match update time with time added to item images (unix epoch) + var fileSystem = new Mock(); + fileSystem.Setup(fs => fs.GetLastWriteTimeUtc(It.IsAny())) + .Returns(oldTime); + BaseItem.FileSystem = fileSystem.Object; + + // all valid paths - matching for strictly updating + var item = GetItemWithImages(imageType, imageCount, true); + // set size to non-zero to allow for updates to occur + foreach (var image in item.GetImages(imageType)) + { + image.DateModified = oldTime; + image.Height = 1; + image.Width = 1; + } + + var images = GetImages(imageType, imageCount, true); + + var itemImageProvider = GetItemImageProvider(null, fileSystem.Object); + var changed = itemImageProvider.MergeImages(item, images); + + Assert.False(changed); + } + + [Theory] + [MemberData(nameof(GetImageTypesWithCount))] + public void MergeImages_PopulatedItemWithGoodPathsAndSameNewImagesWithNewTimestamps_ResetsImageSizes(ImageType imageType, int imageCount) + { + var oldTime = new DateTime(1970, 1, 1); + var updatedTime = new DateTime(2021, 1, 1); + + var fileSystem = new Mock(); + fileSystem.Setup(fs => fs.GetLastWriteTimeUtc(It.IsAny())) + .Returns(updatedTime); + BaseItem.FileSystem = fileSystem.Object; + + // all valid paths - matching for strictly updating + var item = GetItemWithImages(imageType, imageCount, true); + // set size to non-zero to allow for image size reset to occur + foreach (var image in item.GetImages(imageType)) + { + image.DateModified = oldTime; + image.Height = 1; + image.Width = 1; + } + + var images = GetImages(imageType, imageCount, true); + + var itemImageProvider = GetItemImageProvider(null, fileSystem.Object); + var changed = itemImageProvider.MergeImages(item, images); + + Assert.True(changed); + // before and after paths are the same, verify updated by size reset to 0 + Assert.Equal(imageCount, item.GetImages(imageType).Count()); + foreach (var image in item.GetImages(imageType)) + { + Assert.Equal(updatedTime, image.DateModified); + Assert.Equal(0, image.Height); + Assert.Equal(0, image.Width); + } + } + + [Theory] + [MemberData(nameof(GetImageTypesWithCount))] + public async void RefreshImages_PopulatedItemPopulatedProviderDynamic_NoChange(ImageType imageType, int imageCount) + { + var item = GetItemWithImages(imageType, imageCount, true); + + var libraryOptions = GetLibraryOptions(item, imageType, imageCount); + + var dynamicProvider = new Mock(MockBehavior.Strict); + dynamicProvider.Setup(rp => rp.Name).Returns("MockDynamicProvider"); + dynamicProvider.Setup(rp => rp.GetSupportedImages(item)) + .Returns(new[] { imageType }); + + var refreshOptions = new ImageRefreshOptions(null); + + var providerManager = new Mock(MockBehavior.Strict); + providerManager.Setup(pm => pm.SaveImage(item, It.IsAny(), It.IsAny(), imageType, null, It.IsAny())) + .Callback((callbackItem, _, _, callbackType, _, _) => callbackItem.SetImagePath(callbackType, 0, new FileSystemMetadata())) + .Returns(Task.CompletedTask); + var itemImageProvider = GetItemImageProvider(providerManager.Object, null); + var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { dynamicProvider.Object }, refreshOptions, CancellationToken.None); + + Assert.False(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); + Assert.Equal(imageCount, item.GetImages(imageType).Count()); + } + + [Theory] + [MemberData(nameof(GetImageTypesWithCount))] + public async void RefreshImages_EmptyItemPopulatedProviderDynamicWithPath_AddsImages(ImageType imageType, int imageCount) + { + // Has to exist for querying DateModified time on file, results stored but not checked so not populating + BaseItem.FileSystem = Mock.Of(); + + var item = new MovieWithScreenshots(); + + var libraryOptions = GetLibraryOptions(item, imageType, imageCount); + + // Path must exist: is read in as a stream by AsyncFile.OpenRead + var imageResponse = new DynamicImageResponse + { + HasImage = true, + Format = ImageFormat.Jpg, + Path = string.Format(CultureInfo.InvariantCulture, TestDataImagePath, 0), + Protocol = MediaProtocol.File + }; + + var dynamicProvider = new Mock(MockBehavior.Strict); + dynamicProvider.Setup(rp => rp.Name).Returns("MockDynamicProvider"); + dynamicProvider.Setup(rp => rp.GetSupportedImages(item)) + .Returns(new[] { imageType }); + dynamicProvider.Setup(rp => rp.GetImage(item, imageType, It.IsAny())) + .ReturnsAsync(imageResponse); + + var refreshOptions = new ImageRefreshOptions(null); + + var providerManager = new Mock(MockBehavior.Strict); + providerManager.Setup(pm => pm.SaveImage(item, It.IsAny(), It.IsAny(), imageType, null, It.IsAny())) + .Callback((callbackItem, _, _, callbackType, _, _) => callbackItem.SetImagePath(callbackType, 0, new FileSystemMetadata())) + .Returns(Task.CompletedTask); + var itemImageProvider = GetItemImageProvider(providerManager.Object, null); + var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { dynamicProvider.Object }, refreshOptions, CancellationToken.None); + + Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); + // dynamic provider unable to return multiple images + Assert.Single(item.GetImages(imageType)); + } + + [Theory] + [MemberData(nameof(GetImageTypesWithCount))] + public async void RefreshImages_EmptyItemPopulatedProviderDynamicWithoutPath_AddsImages(ImageType imageType, int imageCount) + { + // Has to exist for querying DateModified time on file, results stored but not checked so not populating + BaseItem.FileSystem = Mock.Of(); + + var item = new MovieWithScreenshots(); + + var libraryOptions = GetLibraryOptions(item, imageType, imageCount); + + var imageResponse = new DynamicImageResponse + { + HasImage = true, + Format = ImageFormat.Jpg, + Protocol = MediaProtocol.File + }; + + var dynamicProvider = new Mock(MockBehavior.Strict); + dynamicProvider.Setup(rp => rp.Name).Returns("MockDynamicProvider"); + dynamicProvider.Setup(rp => rp.GetSupportedImages(item)) + .Returns(new[] { imageType }); + dynamicProvider.Setup(rp => rp.GetImage(item, imageType, It.IsAny())) + .ReturnsAsync(imageResponse); + + var refreshOptions = new ImageRefreshOptions(null); + + var providerManager = new Mock(MockBehavior.Strict); + providerManager.Setup(pm => pm.SaveImage(item, It.IsAny(), It.IsAny(), imageType, null, It.IsAny())) + .Callback((callbackItem, _, _, callbackType, _, _) => callbackItem.SetImagePath(callbackType, 0, new FileSystemMetadata())) + .Returns(Task.CompletedTask); + var itemImageProvider = GetItemImageProvider(providerManager.Object, null); + var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { dynamicProvider.Object }, refreshOptions, CancellationToken.None); + + Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); + // dynamic provider unable to return multiple images + Assert.Single(item.GetImages(imageType)); + } + + [Theory] + [MemberData(nameof(GetImageTypesWithCount))] + public async void RefreshImages_PopulatedItemPopulatedProviderDynamicFullRefresh_UpdatesImages(ImageType imageType, int imageCount) + { + var item = GetItemWithImages(imageType, imageCount, false); + + var libraryOptions = GetLibraryOptions(item, imageType, imageCount); + + var expectedPath = "dynamic response path url"; + var imageResponse = new DynamicImageResponse + { + HasImage = true, + Format = ImageFormat.Jpg, + Path = expectedPath, + Protocol = MediaProtocol.Http + }; + + var dynamicProvider = new Mock(MockBehavior.Strict); + dynamicProvider.Setup(rp => rp.Name).Returns("MockDynamicProvider"); + dynamicProvider.Setup(rp => rp.GetSupportedImages(item)) + .Returns(new[] { imageType }); + dynamicProvider.Setup(rp => rp.GetImage(item, imageType, It.IsAny())) + .ReturnsAsync(imageResponse); + + var refreshOptions = new ImageRefreshOptions(null) + { + ImageRefreshMode = MetadataRefreshMode.FullRefresh, + ReplaceAllImages = true + }; + + var itemImageProvider = GetItemImageProvider(null, Mock.Of()); + var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { dynamicProvider.Object }, refreshOptions, CancellationToken.None); + + Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); + // dynamic provider unable to return multiple images + Assert.Single(item.GetImages(imageType)); + Assert.Equal(expectedPath, item.GetImagePath(imageType, 0)); + } + + [Theory] + [MemberData(nameof(GetImageTypesWithCount))] + public async void RefreshImages_PopulatedItemPopulatedProviderRemote_NoChange(ImageType imageType, int imageCount) + { + var item = GetItemWithImages(imageType, imageCount, false); + + var libraryOptions = GetLibraryOptions(item, imageType, imageCount); + + var remoteProvider = new Mock(MockBehavior.Strict); + remoteProvider.Setup(rp => rp.Name).Returns("MockRemoteProvider"); + remoteProvider.Setup(rp => rp.GetSupportedImages(item)) + .Returns(new[] { imageType }); + + var refreshOptions = new ImageRefreshOptions(null); + + var remoteInfo = new List(); + for (int i = 0; i < imageCount; i++) + { + remoteInfo.Add(new RemoteImageInfo + { + Type = imageType, + Url = "image url " + i, + Width = 1 // min width is set to 0, this will always pass + }); + } + + var providerManager = new Mock(MockBehavior.Strict); + providerManager.Setup(pm => pm.GetAvailableRemoteImages(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(remoteInfo); + var itemImageProvider = GetItemImageProvider(providerManager.Object, Mock.Of()); + var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { remoteProvider.Object }, refreshOptions, CancellationToken.None); + + Assert.False(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); + Assert.Equal(imageCount, item.GetImages(imageType).Count()); + } + + [Theory] + [MemberData(nameof(GetImageTypesWithCount))] + public async void RefreshImages_EmptyNonStubItemPopulatedProviderRemote_DownloadsImages(ImageType imageType, int imageCount) + { + // Has to exist for querying DateModified time on file, results stored but not checked so not populating + BaseItem.FileSystem ??= Mock.Of(); + + // Set path and media source manager so images will be downloaded (EnableImageStub will return false) + var item = new MovieWithScreenshots + { + Path = "non-empty path" + }; + BaseItem.MediaSourceManager = Mock.Of(); + + var libraryOptions = GetLibraryOptions(item, imageType, imageCount); + + var remoteProvider = new Mock(MockBehavior.Strict); + remoteProvider.Setup(rp => rp.Name).Returns("MockRemoteProvider"); + remoteProvider.Setup(rp => rp.GetSupportedImages(item)) + .Returns(new[] { imageType }); + remoteProvider.Setup(rp => rp.GetImageResponse(It.IsAny(), It.IsAny())) + .ReturnsAsync((string url, CancellationToken _) => new HttpResponseMessage + { + ReasonPhrase = url, + StatusCode = HttpStatusCode.OK, + Content = new StringContent("Content", Encoding.UTF8, "image/jpeg") + }); + + var refreshOptions = new ImageRefreshOptions(null); + + var remoteInfo = new List(); + for (int i = 0; i < imageCount; i++) + { + remoteInfo.Add(new RemoteImageInfo + { + Type = imageType, + Url = "image url " + i, + Width = 1 // min width is set to 0, this will always pass + }); + } + + var providerManager = new Mock(MockBehavior.Strict); + providerManager.Setup(pm => pm.GetAvailableRemoteImages(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(remoteInfo); + providerManager.Setup(pm => pm.SaveImage(item, It.IsAny(), It.IsAny(), imageType, null, It.IsAny())) + .Callback((callbackItem, _, _, callbackType, _, _) => + callbackItem.SetImagePath(callbackType, callbackItem.AllowsMultipleImages(callbackType) ? callbackItem.GetImages(callbackType).Count() : 0, new FileSystemMetadata())) + .Returns(Task.CompletedTask); + var fileSystem = new Mock(); + fileSystem.Setup(fs => fs.GetFileInfo(It.IsAny())) + .Returns(new FileSystemMetadata { Length = 1 }); + var itemImageProvider = GetItemImageProvider(providerManager.Object, fileSystem.Object); + var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { remoteProvider.Object }, refreshOptions, CancellationToken.None); + + Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); + Assert.Equal(imageCount, item.GetImages(imageType).Count()); + } + + [Theory] + [MemberData(nameof(GetImageTypesWithCount))] + public async void RefreshImages_EmptyItemPopulatedProviderRemoteExtras_LimitsImages(ImageType imageType, int imageCount) + { + var item = new MovieWithScreenshots(); + + var libraryOptions = GetLibraryOptions(item, imageType, imageCount); + + var remoteProvider = new Mock(MockBehavior.Strict); + remoteProvider.Setup(rp => rp.Name).Returns("MockRemoteProvider"); + remoteProvider.Setup(rp => rp.GetSupportedImages(item)) + .Returns(new[] { imageType }); + + var refreshOptions = new ImageRefreshOptions(null); + + // populate remote with double the required images to verify count is trimmed to the library option count + var remoteInfo = new List(); + for (int i = 0; i < imageCount * 2; i++) + { + remoteInfo.Add(new RemoteImageInfo + { + Type = imageType, + Url = "image url " + i, + Width = 1 // min width is set to 0, this will always pass + }); + } + + var providerManager = new Mock(MockBehavior.Strict); + providerManager.Setup(pm => pm.GetAvailableRemoteImages(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(remoteInfo); + var itemImageProvider = GetItemImageProvider(providerManager.Object, Mock.Of()); + var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { remoteProvider.Object }, refreshOptions, CancellationToken.None); + + Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); + var actualImages = item.GetImages(imageType).ToList(); + Assert.Equal(imageCount, actualImages.Count); + // images from the provider manager are sorted by preference (earlier images are higher priority) so we can verify that low url numbers are chosen + foreach (var image in actualImages) + { + var index = int.Parse(Regex.Match(image.Path, @"\d+").Value, NumberStyles.Integer, CultureInfo.InvariantCulture); + Assert.True(index < imageCount); + } + } + + [Theory] + [MemberData(nameof(GetImageTypesWithCount))] + public async void RefreshImages_PopulatedItemPopulatedProviderRemoteFullRefresh_UpdatesImages(ImageType imageType, int imageCount) + { + var item = GetItemWithImages(imageType, imageCount, false); + + var libraryOptions = GetLibraryOptions(item, imageType, imageCount); + + var remoteProvider = new Mock(MockBehavior.Strict); + remoteProvider.Setup(rp => rp.Name).Returns("MockRemoteProvider"); + remoteProvider.Setup(rp => rp.GetSupportedImages(item)) + .Returns(new[] { imageType }); + + var refreshOptions = new ImageRefreshOptions(null) + { + ImageRefreshMode = MetadataRefreshMode.FullRefresh, + ReplaceAllImages = true + }; + + var remoteInfo = new List(); + for (int i = 0; i < imageCount; i++) + { + remoteInfo.Add(new RemoteImageInfo + { + Type = imageType, + Url = "image url " + i, + Width = 1 // min width is set to 0, this will always pass + }); + } + + var providerManager = new Mock(MockBehavior.Strict); + providerManager.Setup(pm => pm.GetAvailableRemoteImages(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(remoteInfo); + var itemImageProvider = GetItemImageProvider(providerManager.Object, Mock.Of()); + var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { remoteProvider.Object }, refreshOptions, CancellationToken.None); + + Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); + Assert.Equal(imageCount, item.GetImages(imageType).Count()); + foreach (var image in item.GetImages(imageType)) + { + Assert.Matches(@"image url \d", image.Path); + } + } + + [Theory] + [MemberData(nameof(GetImageTypesWithCount))] + public async void RefreshImages_PopulatedItemEmptyProviderRemoteFullRefresh_DoesntClearImages(ImageType imageType, int imageCount) + { + var item = GetItemWithImages(imageType, imageCount, false); + + var libraryOptions = GetLibraryOptions(item, imageType, imageCount); + + var remoteProvider = new Mock(MockBehavior.Strict); + remoteProvider.Setup(rp => rp.Name).Returns("MockRemoteProvider"); + remoteProvider.Setup(rp => rp.GetSupportedImages(item)) + .Returns(new[] { imageType }); + + var refreshOptions = new ImageRefreshOptions(null) + { + ImageRefreshMode = MetadataRefreshMode.FullRefresh, + ReplaceAllImages = true + }; + + var itemImageProvider = GetItemImageProvider(Mock.Of(), Mock.Of()); + var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { remoteProvider.Object }, refreshOptions, CancellationToken.None); + + Assert.False(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); + Assert.Equal(imageCount, item.GetImages(imageType).Count()); + } + + private static ItemImageProvider GetItemImageProvider(IProviderManager? providerManager, IFileSystem? fileSystem) + { + // strict to ensure this isn't accidentally used where a prepared mock is intended + providerManager ??= Mock.Of(MockBehavior.Strict); + fileSystem ??= Mock.Of(MockBehavior.Strict); + return new ItemImageProvider(new NullLogger(), providerManager, fileSystem); + } + + private static BaseItem GetItemWithImages(ImageType type, int count, bool validPaths) + { + // Has to exist for querying DateModified time on file, results stored but not checked so not populating + BaseItem.FileSystem ??= Mock.Of(); + + var item = new MovieWithScreenshots(); + + var path = validPaths ? TestDataImagePath : "invalid path {0}"; + for (int i = 0; i < count; i++) + { + item.SetImagePath(type, i, new FileSystemMetadata + { + FullName = string.Format(CultureInfo.InvariantCulture, path, i), + }); + } + + return item; + } + + private static ILocalImageProvider GetImageProvider(ImageType type, int count, bool validPaths) + { + var images = GetImages(type, count, validPaths); + + var imageProvider = new Mock(); + imageProvider.Setup(ip => ip.GetImages(It.IsAny(), It.IsAny())) + .Returns(images); + return imageProvider.Object; + } + + /// + /// Creates a list of references of the specified type and size, optionally pointing to files that exist. + /// + private static List GetImages(ImageType type, int count, bool validPaths) + { + var path = validPaths ? TestDataImagePath : "invalid path {0}"; + var images = new List(count); + for (int i = 0; i < count; i++) + { + images.Add(new LocalImageInfo + { + Type = type, + FileInfo = new FileSystemMetadata + { + FullName = string.Format(CultureInfo.InvariantCulture, path, i) + } + }); + } + + return images; + } + + /// + /// Generates a object that will allow for the requested number of images for the target type. + /// + private static LibraryOptions GetLibraryOptions(BaseItem item, ImageType type, int count) + { + return new LibraryOptions + { + TypeOptions = new[] + { + new TypeOptions + { + Type = item.GetType().Name, + ImageOptions = new[] + { + new ImageOption + { + Type = type, + Limit = count, + MinWidth = 0 + } + } + } + } + }; + } + + // Create a class that implements IHasScreenshots for testing since no BaseItem class is also IHasScreenshots + private class MovieWithScreenshots : Movie, IHasScreenshots + { + // No contents + } + } +} diff --git a/tests/Jellyfin.Providers.Tests/Test Data/Images/blank0.jpg b/tests/Jellyfin.Providers.Tests/Test Data/Images/blank0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/Jellyfin.Providers.Tests/Test Data/Images/blank1.jpg b/tests/Jellyfin.Providers.Tests/Test Data/Images/blank1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 From 0fbd8d85c825b2871ea38e5c7c1d61baca0772c9 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Mon, 1 Nov 2021 00:16:11 +0100 Subject: [PATCH 244/278] Validate multi-images, lazy-delete bg on refresh Fix failing test: Invalid background images not purged by validate Fixes #6310: Background images only delete when using "Replace existing images" when new image(s) is found to replace them --- .../Manager/ItemImageProvider.cs | 68 +++++++++++-------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 49b7a5d6b81..c80407bcb4a 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -103,14 +103,16 @@ namespace MediaBrowser.Providers.Manager ImageRefreshOptions refreshOptions, CancellationToken cancellationToken) { + List oldBackdropImages = new List(); if (refreshOptions.IsReplacingImage(ImageType.Backdrop)) { - ClearImages(item, ImageType.Backdrop); + oldBackdropImages = item.GetImages(ImageType.Backdrop).ToList(); } + List oldScreenshotImages = new List(); if (refreshOptions.IsReplacingImage(ImageType.Screenshot)) { - ClearImages(item, ImageType.Screenshot); + oldScreenshotImages = item.GetImages(ImageType.Screenshot).ToList(); } var result = new RefreshResult { UpdateType = ItemUpdateType.None }; @@ -118,9 +120,9 @@ namespace MediaBrowser.Providers.Manager var typeName = item.GetType().Name; var typeOptions = libraryOptions.GetTypeOptions(typeName) ?? new TypeOptions { Type = typeName }; - // In order to avoid duplicates, only download these if there are none already - var backdropLimit = typeOptions.GetLimit(ImageType.Backdrop); - var screenshotLimit = typeOptions.GetLimit(ImageType.Screenshot); + // track library limits, adding buffer to allow lazy replacing of current images + var backdropLimit = typeOptions.GetLimit(ImageType.Backdrop) + oldBackdropImages.Count; + var screenshotLimit = typeOptions.GetLimit(ImageType.Screenshot) + oldScreenshotImages.Count; var downloadedImages = new List(); foreach (var provider in providers) @@ -137,6 +139,17 @@ namespace MediaBrowser.Providers.Manager } } + // only delete existing multi-images if new ones were added + if (oldBackdropImages.Count > 0 && oldBackdropImages.Count < item.GetImages(ImageType.Backdrop).Count()) + { + PruneImages(item, oldBackdropImages); + } + + if (oldScreenshotImages.Count > 0 && oldScreenshotImages.Count < item.GetImages(ImageType.Screenshot).Count()) + { + PruneImages(item, oldScreenshotImages); + } + return result; } @@ -176,13 +189,14 @@ namespace MediaBrowser.Providers.Manager if (response.Protocol == MediaProtocol.Http) { _logger.LogDebug("Setting image url into item {0}", item.Id); + var index = item.AllowsMultipleImages(imageType) ? item.GetImages(imageType).Count() : 0; item.SetImage( new ItemImageInfo { Path = response.Path, Type = imageType }, - 0); + index); } else { @@ -352,35 +366,25 @@ namespace MediaBrowser.Providers.Manager return options.IsEnabled(type); } - private void ClearImages(BaseItem item, ImageType type) + private void PruneImages(BaseItem item, List images) { - var deleted = false; - var deletedImages = new List(); - - foreach (var image in item.GetImages(type)) + for (var i = 0; i < images.Count; i++) { - if (!image.IsLocalFile) - { - deletedImages.Add(image); - continue; - } + var image = images[i]; - try - { - _fileSystem.DeleteFile(image.Path); - deleted = true; - } - catch (FileNotFoundException) + if (image.IsLocalFile) { + try + { + _fileSystem.DeleteFile(image.Path); + } + catch (FileNotFoundException) + { + } } } - item.RemoveImages(deletedImages); - - if (deleted) - { - item.ValidateImages(new DirectoryService(_fileSystem)); - } + item.RemoveImages(images); } /// @@ -476,6 +480,14 @@ namespace MediaBrowser.Providers.Manager { var changed = false; + var deletedImages = item.GetImages(type).Where(i => i.IsLocalFile && !File.Exists(i.Path)).ToList(); + + if (deletedImages.Count > 0) + { + item.RemoveImages(deletedImages); + changed = true; + } + var newImageFileInfos = images .Where(i => i.Type == type) .Select(i => i.FileInfo) From b478b115e3194aa383f86d7d6fbf07e0f2bfadea Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Mon, 1 Nov 2021 02:38:12 +0100 Subject: [PATCH 245/278] Refactor to validate all images up front --- MediaBrowser.Controller/Entities/BaseItem.cs | 19 ++-------- .../Manager/ItemImageProvider.cs | 29 ++------------- .../Manager/ItemImageProviderTests.cs | 35 ++++++++++++------- 3 files changed, 27 insertions(+), 56 deletions(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 838a9f2f8d6..7dd8e310ed2 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2495,11 +2495,11 @@ namespace MediaBrowser.Controller.Entities } /// - /// Adds the images. + /// Adds the images, updating metadata if they already are part of this item. /// /// Type of the image. /// The images. - /// true if XXXX, false otherwise. + /// true if images were added or updated, false otherwise. /// Cannot call AddImages with chapter images. public bool AddImages(ImageType imageType, List images) { @@ -2512,7 +2512,6 @@ namespace MediaBrowser.Controller.Entities .ToList(); var newImageList = new List(); - var imageAdded = false; var imageUpdated = false; foreach (var newImage in images) @@ -2528,7 +2527,6 @@ namespace MediaBrowser.Controller.Entities if (existing == null) { newImageList.Add(newImage); - imageAdded = true; } else { @@ -2549,19 +2547,6 @@ namespace MediaBrowser.Controller.Entities } } - if (imageAdded || images.Count != existingImages.Count) - { - var newImagePaths = images.Select(i => i.FullName).ToList(); - - var deleted = existingImages - .FindAll(i => i.IsLocalFile && !newImagePaths.Contains(i.Path.AsSpan(), StringComparison.OrdinalIgnoreCase) && !File.Exists(i.Path)); - - if (deleted.Count > 0) - { - ImageInfos = ImageInfos.Except(deleted).ToArray(); - } - } - if (newImageList.Count > 0) { ImageInfos = ImageInfos.Concat(newImageList.Select(i => GetImageInfo(i, imageType))).ToArray(); diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index c80407bcb4a..f60fce11b86 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -395,7 +395,7 @@ namespace MediaBrowser.Providers.Manager /// true if changes were made to the item; otherwise false. public bool MergeImages(BaseItem item, IReadOnlyList images) { - var changed = false; + var changed = item.ValidateImages(new DirectoryService(_fileSystem)); for (var i = 0; i < _singularImages.Length; i++) { @@ -431,18 +431,6 @@ namespace MediaBrowser.Providers.Manager currentImage.DateModified = newDateModified; } } - else - { - var existing = item.GetImageInfo(type, 0); - if (existing != null) - { - if (existing.IsLocalFile && !File.Exists(existing.Path)) - { - item.RemoveImage(existing); - changed = true; - } - } - } } if (UpdateMultiImages(item, images, ImageType.Backdrop)) @@ -450,12 +438,9 @@ namespace MediaBrowser.Providers.Manager changed = true; } - if (item is IHasScreenshots) + if (item is IHasScreenshots && UpdateMultiImages(item, images, ImageType.Screenshot)) { - if (UpdateMultiImages(item, images, ImageType.Screenshot)) - { - changed = true; - } + changed = true; } return changed; @@ -480,14 +465,6 @@ namespace MediaBrowser.Providers.Manager { var changed = false; - var deletedImages = item.GetImages(type).Where(i => i.IsLocalFile && !File.Exists(i.Path)).ToList(); - - if (deletedImages.Count > 0) - { - item.RemoveImages(deletedImages); - changed = true; - } - var newImageFileInfos = images .Where(i => i.Type == type) .Select(i => i.FileInfo) diff --git a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs index 253bcb7cadc..b5efd8f0133 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs @@ -183,7 +183,7 @@ namespace Jellyfin.Providers.Tests.Manager var images = GetImages(imageType, imageCount, true); - var itemImageProvider = GetItemImageProvider(null, fileSystem.Object); + var itemImageProvider = GetItemImageProvider(null, fileSystem); var changed = itemImageProvider.MergeImages(item, images); Assert.False(changed); @@ -213,7 +213,7 @@ namespace Jellyfin.Providers.Tests.Manager var images = GetImages(imageType, imageCount, true); - var itemImageProvider = GetItemImageProvider(null, fileSystem.Object); + var itemImageProvider = GetItemImageProvider(null, fileSystem); var changed = itemImageProvider.MergeImages(item, images); Assert.True(changed); @@ -363,7 +363,7 @@ namespace Jellyfin.Providers.Tests.Manager ReplaceAllImages = true }; - var itemImageProvider = GetItemImageProvider(null, Mock.Of()); + var itemImageProvider = GetItemImageProvider(null, new Mock()); var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { dynamicProvider.Object }, refreshOptions, CancellationToken.None); Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); @@ -401,7 +401,7 @@ namespace Jellyfin.Providers.Tests.Manager var providerManager = new Mock(MockBehavior.Strict); providerManager.Setup(pm => pm.GetAvailableRemoteImages(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(remoteInfo); - var itemImageProvider = GetItemImageProvider(providerManager.Object, Mock.Of()); + var itemImageProvider = GetItemImageProvider(providerManager.Object, null); var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { remoteProvider.Object }, refreshOptions, CancellationToken.None); Assert.False(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); @@ -459,7 +459,7 @@ namespace Jellyfin.Providers.Tests.Manager var fileSystem = new Mock(); fileSystem.Setup(fs => fs.GetFileInfo(It.IsAny())) .Returns(new FileSystemMetadata { Length = 1 }); - var itemImageProvider = GetItemImageProvider(providerManager.Object, fileSystem.Object); + var itemImageProvider = GetItemImageProvider(providerManager.Object, fileSystem); var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { remoteProvider.Object }, refreshOptions, CancellationToken.None); Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); @@ -496,7 +496,7 @@ namespace Jellyfin.Providers.Tests.Manager var providerManager = new Mock(MockBehavior.Strict); providerManager.Setup(pm => pm.GetAvailableRemoteImages(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(remoteInfo); - var itemImageProvider = GetItemImageProvider(providerManager.Object, Mock.Of()); + var itemImageProvider = GetItemImageProvider(providerManager.Object, null); var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { remoteProvider.Object }, refreshOptions, CancellationToken.None); Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); @@ -505,7 +505,7 @@ namespace Jellyfin.Providers.Tests.Manager // images from the provider manager are sorted by preference (earlier images are higher priority) so we can verify that low url numbers are chosen foreach (var image in actualImages) { - var index = int.Parse(Regex.Match(image.Path, @"\d+").Value, NumberStyles.Integer, CultureInfo.InvariantCulture); + var index = int.Parse(Regex.Match(image.Path, @"[0-9]+").Value, NumberStyles.Integer, CultureInfo.InvariantCulture); Assert.True(index < imageCount); } } @@ -543,14 +543,14 @@ namespace Jellyfin.Providers.Tests.Manager var providerManager = new Mock(MockBehavior.Strict); providerManager.Setup(pm => pm.GetAvailableRemoteImages(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(remoteInfo); - var itemImageProvider = GetItemImageProvider(providerManager.Object, Mock.Of()); + var itemImageProvider = GetItemImageProvider(providerManager.Object, new Mock()); var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { remoteProvider.Object }, refreshOptions, CancellationToken.None); Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); Assert.Equal(imageCount, item.GetImages(imageType).Count()); foreach (var image in item.GetImages(imageType)) { - Assert.Matches(@"image url \d", image.Path); + Assert.Matches(@"image url [0-9]", image.Path); } } @@ -573,19 +573,28 @@ namespace Jellyfin.Providers.Tests.Manager ReplaceAllImages = true }; - var itemImageProvider = GetItemImageProvider(Mock.Of(), Mock.Of()); + var itemImageProvider = GetItemImageProvider(Mock.Of(), null); var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { remoteProvider.Object }, refreshOptions, CancellationToken.None); Assert.False(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); Assert.Equal(imageCount, item.GetImages(imageType).Count()); } - private static ItemImageProvider GetItemImageProvider(IProviderManager? providerManager, IFileSystem? fileSystem) + private static ItemImageProvider GetItemImageProvider(IProviderManager? providerManager, Mock? mockFileSystem) { // strict to ensure this isn't accidentally used where a prepared mock is intended providerManager ??= Mock.Of(MockBehavior.Strict); - fileSystem ??= Mock.Of(MockBehavior.Strict); - return new ItemImageProvider(new NullLogger(), providerManager, fileSystem); + + // BaseItem.ValidateImages depends on the directory service being able to list directory contents, give it the expected valid file paths + mockFileSystem ??= new Mock(MockBehavior.Strict); + mockFileSystem.Setup(fs => fs.GetFilePaths(It.IsAny(), It.IsAny())) + .Returns(new[] + { + string.Format(CultureInfo.InvariantCulture, TestDataImagePath, 0), + string.Format(CultureInfo.InvariantCulture, TestDataImagePath, 1) + }); + + return new ItemImageProvider(new NullLogger(), providerManager, mockFileSystem.Object); } private static BaseItem GetItemWithImages(ImageType type, int count, bool validPaths) From baafa10e878a061be7773bdb42706cb020e4d0b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 12:00:53 +0000 Subject: [PATCH 246/278] Bump Microsoft.NET.Test.Sdk from 16.11.0 to 17.0.0 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.11.0 to 17.0.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.11.0...v17.0.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj | 2 +- .../Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj | 2 +- tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj | 2 +- .../Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj | 2 +- .../Jellyfin.MediaEncoding.Tests.csproj | 2 +- tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj | 2 +- tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj | 2 +- .../Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj | 2 +- tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj | 2 +- .../Jellyfin.Server.Implementations.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 2 +- tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 2 +- .../Jellyfin.XbmcMetadata.Tests.csproj | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 922b3d94f76..57ec86316cd 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index 1fe4e256561..ce607b2ec95 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj index e9a95157106..0ffc19833ad 100644 --- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj index 1fb95aab4d8..0981660012e 100644 --- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj +++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj index 2dc4ac19a5a..ee3af7559e4 100644 --- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj +++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj @@ -7,7 +7,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj index 201f63a2d6b..dc4a42c19fe 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj @@ -22,7 +22,7 @@ - + diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj index a37e5ac9205..7e8397d9f71 100644 --- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj +++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj index 75d466198a0..4096873a37f 100644 --- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj index 75d9b9ea900..78556ee675b 100644 --- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj +++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj index 0b2db64b0bb..bb88ec6a1df 100644 --- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj +++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 5ecd8460470..028ebdf5545 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -21,7 +21,7 @@ - + diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 9d7b447edd5..889220d86ef 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 67ae0e0802a..3daa45e56cd 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj index 94294c8bf30..edf9e0fefbe 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj +++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj @@ -13,7 +13,7 @@ - + From 8e046ce22b295c552b6f21be91db80e1fd2bce6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 12:00:57 +0000 Subject: [PATCH 247/278] Bump SQLitePCLRaw.bundle_e_sqlite3 from 2.0.6 to 2.0.7 Bumps [SQLitePCLRaw.bundle_e_sqlite3](https://github.com/ericsink/SQLitePCL.raw) from 2.0.6 to 2.0.7. - [Release notes](https://github.com/ericsink/SQLitePCL.raw/releases) - [Commits](https://github.com/ericsink/SQLitePCL.raw/compare/v2.0.6...v2.0.7) --- updated-dependencies: - dependency-name: SQLitePCLRaw.bundle_e_sqlite3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 8983eb50fdd..49d979e1154 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -44,7 +44,7 @@ - + From 2372931b13d2ec74fafcffe6a07bd157b911ef62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 12:01:04 +0000 Subject: [PATCH 248/278] Bump UTF.Unknown from 2.4.0 to 2.5.0 Bumps [UTF.Unknown](https://github.com/CharsetDetector/UTF-unknown) from 2.4.0 to 2.5.0. - [Release notes](https://github.com/CharsetDetector/UTF-unknown/releases) - [Commits](https://github.com/CharsetDetector/UTF-unknown/compare/v2.4...v2.5) --- updated-dependencies: - dependency-name: UTF.Unknown dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index a6caca8db7c..c1fd8e5fb46 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -26,7 +26,7 @@ - + From a229526454750de978397571b0b19426f195dd3d Mon Sep 17 00:00:00 2001 From: WWWesten <4700006+WWWesten@users.noreply.github.com> Date: Mon, 1 Nov 2021 23:47:31 +0500 Subject: [PATCH 249/278] Update LocalizationManager.cs --- .../Localization/LocalizationManager.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 03919197e24..a5a530a5652 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -372,9 +372,11 @@ namespace Emby.Server.Implementations.Localization /// public IEnumerable GetLocalizationOptions() { - yield return new LocalizationOption("Arabic", "ar"); + yield return new LocalizationOption("Afrikaans", "af"); + yield return new LocalizationOption("Arabic", "ar"); yield return new LocalizationOption("Bulgarian (Bulgaria)", "bg-BG"); yield return new LocalizationOption("Catalan", "ca"); + yield return new LocalizationOption("Chinese (Hong Kong)", "zh-HK"); yield return new LocalizationOption("Chinese Simplified", "zh-CN"); yield return new LocalizationOption("Chinese Traditional", "zh-TW"); yield return new LocalizationOption("Croatian", "hr"); @@ -383,32 +385,48 @@ namespace Emby.Server.Implementations.Localization yield return new LocalizationOption("Dutch", "nl"); yield return new LocalizationOption("English (United Kingdom)", "en-GB"); yield return new LocalizationOption("English (United States)", "en-US"); + yield return new LocalizationOption("Esperanto", "eo"); + yield return new LocalizationOption("Estonian", "et"); + yield return new LocalizationOption("Finnish", "fi"); yield return new LocalizationOption("French", "fr"); yield return new LocalizationOption("French (Canada)", "fr-CA"); yield return new LocalizationOption("German", "de"); yield return new LocalizationOption("Greek", "el"); yield return new LocalizationOption("Hebrew", "he"); yield return new LocalizationOption("Hungarian", "hu"); + yield return new LocalizationOption("Icelandic", "is"); + yield return new LocalizationOption("Indonesian", "id"); yield return new LocalizationOption("Italian", "it"); + yield return new LocalizationOption("Japanese", "ja"); yield return new LocalizationOption("Kazakh", "kk"); yield return new LocalizationOption("Korean", "ko"); + yield return new LocalizationOption("Latvian", "lv"); yield return new LocalizationOption("Lithuanian", "lt-LT"); yield return new LocalizationOption("Malay", "ms"); + yield return new LocalizationOption("Malayalam", "ml"); yield return new LocalizationOption("Norwegian Bokmål", "nb"); + yield return new LocalizationOption("Norwegian Nynorsk", "nn"); yield return new LocalizationOption("Persian", "fa"); yield return new LocalizationOption("Polish", "pl"); + yield return new LocalizationOption("Portuguese", "pt"); yield return new LocalizationOption("Portuguese (Brazil)", "pt-BR"); yield return new LocalizationOption("Portuguese (Portugal)", "pt-PT"); + yield return new LocalizationOption("Romanian", "ro"); yield return new LocalizationOption("Russian", "ru"); + yield return new LocalizationOption("Serbian", "sr"); yield return new LocalizationOption("Slovak", "sk"); yield return new LocalizationOption("Slovenian (Slovenia)", "sl-SI"); yield return new LocalizationOption("Spanish", "es"); yield return new LocalizationOption("Spanish (Argentina)", "es-AR"); + yield return new LocalizationOption("Spanish (Latin America)", "es-419"); yield return new LocalizationOption("Spanish (Mexico)", "es-MX"); yield return new LocalizationOption("Swedish", "sv"); yield return new LocalizationOption("Swiss German", "gsw"); + yield return new LocalizationOption("Tamil", "ta"); + yield return new LocalizationOption("Telugu", "te"); yield return new LocalizationOption("Turkish", "tr"); yield return new LocalizationOption("Tiếng Việt", "vi"); + yield return new LocalizationOption("Ukrainian", "uk"); } } } From 967fd66ca9b416bb8ca0796304ab912a45f3dd1d Mon Sep 17 00:00:00 2001 From: WWWesten <4700006+WWWesten@users.noreply.github.com> Date: Tue, 2 Nov 2021 00:22:16 +0500 Subject: [PATCH 250/278] Update LocalizationManager.cs --- .../Localization/LocalizationManager.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index a5a530a5652..1524fcdb2bc 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -372,11 +372,11 @@ namespace Emby.Server.Implementations.Localization /// public IEnumerable GetLocalizationOptions() { - yield return new LocalizationOption("Afrikaans", "af"); - yield return new LocalizationOption("Arabic", "ar"); + yield return new LocalizationOption("Afrikaans", "af"); + yield return new LocalizationOption("Arabic", "ar"); yield return new LocalizationOption("Bulgarian (Bulgaria)", "bg-BG"); yield return new LocalizationOption("Catalan", "ca"); - yield return new LocalizationOption("Chinese (Hong Kong)", "zh-HK"); + yield return new LocalizationOption("Chinese (Hong Kong)", "zh-HK"); yield return new LocalizationOption("Chinese Simplified", "zh-CN"); yield return new LocalizationOption("Chinese Traditional", "zh-TW"); yield return new LocalizationOption("Croatian", "hr"); @@ -385,48 +385,48 @@ namespace Emby.Server.Implementations.Localization yield return new LocalizationOption("Dutch", "nl"); yield return new LocalizationOption("English (United Kingdom)", "en-GB"); yield return new LocalizationOption("English (United States)", "en-US"); - yield return new LocalizationOption("Esperanto", "eo"); - yield return new LocalizationOption("Estonian", "et"); - yield return new LocalizationOption("Finnish", "fi"); + yield return new LocalizationOption("Esperanto", "eo"); + yield return new LocalizationOption("Estonian", "et"); + yield return new LocalizationOption("Finnish", "fi"); yield return new LocalizationOption("French", "fr"); yield return new LocalizationOption("French (Canada)", "fr-CA"); yield return new LocalizationOption("German", "de"); yield return new LocalizationOption("Greek", "el"); yield return new LocalizationOption("Hebrew", "he"); yield return new LocalizationOption("Hungarian", "hu"); - yield return new LocalizationOption("Icelandic", "is"); - yield return new LocalizationOption("Indonesian", "id"); + yield return new LocalizationOption("Icelandic", "is"); + yield return new LocalizationOption("Indonesian", "id"); yield return new LocalizationOption("Italian", "it"); - yield return new LocalizationOption("Japanese", "ja"); + yield return new LocalizationOption("Japanese", "ja"); yield return new LocalizationOption("Kazakh", "kk"); yield return new LocalizationOption("Korean", "ko"); - yield return new LocalizationOption("Latvian", "lv"); + yield return new LocalizationOption("Latvian", "lv"); yield return new LocalizationOption("Lithuanian", "lt-LT"); yield return new LocalizationOption("Malay", "ms"); - yield return new LocalizationOption("Malayalam", "ml"); + yield return new LocalizationOption("Malayalam", "ml"); yield return new LocalizationOption("Norwegian Bokmål", "nb"); - yield return new LocalizationOption("Norwegian Nynorsk", "nn"); + yield return new LocalizationOption("Norwegian Nynorsk", "nn"); yield return new LocalizationOption("Persian", "fa"); yield return new LocalizationOption("Polish", "pl"); - yield return new LocalizationOption("Portuguese", "pt"); + yield return new LocalizationOption("Portuguese", "pt"); yield return new LocalizationOption("Portuguese (Brazil)", "pt-BR"); yield return new LocalizationOption("Portuguese (Portugal)", "pt-PT"); - yield return new LocalizationOption("Romanian", "ro"); + yield return new LocalizationOption("Romanian", "ro"); yield return new LocalizationOption("Russian", "ru"); - yield return new LocalizationOption("Serbian", "sr"); + yield return new LocalizationOption("Serbian", "sr"); yield return new LocalizationOption("Slovak", "sk"); yield return new LocalizationOption("Slovenian (Slovenia)", "sl-SI"); yield return new LocalizationOption("Spanish", "es"); yield return new LocalizationOption("Spanish (Argentina)", "es-AR"); - yield return new LocalizationOption("Spanish (Latin America)", "es-419"); + yield return new LocalizationOption("Spanish (Latin America)", "es-419"); yield return new LocalizationOption("Spanish (Mexico)", "es-MX"); yield return new LocalizationOption("Swedish", "sv"); yield return new LocalizationOption("Swiss German", "gsw"); - yield return new LocalizationOption("Tamil", "ta"); - yield return new LocalizationOption("Telugu", "te"); + yield return new LocalizationOption("Tamil", "ta"); + yield return new LocalizationOption("Telugu", "te"); yield return new LocalizationOption("Turkish", "tr"); yield return new LocalizationOption("Tiếng Việt", "vi"); - yield return new LocalizationOption("Ukrainian", "uk"); + yield return new LocalizationOption("Ukrainian", "uk"); } } } From 5529625025f53b6397c500d8cce2b3c5be28e63c Mon Sep 17 00:00:00 2001 From: Pedro Almeida Date: Mon, 1 Nov 2021 15:38:14 +0000 Subject: [PATCH 251/278] Translated using Weblate (Portuguese (Portugal)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_PT/ --- Emby.Server.Implementations/Localization/Core/pt-PT.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json index 8c41edf963c..525a02c8839 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-PT.json +++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json @@ -39,7 +39,7 @@ "MixedContent": "Conteúdo Misto", "Movies": "Filmes", "Music": "Música", - "MusicVideos": "Videoclips", + "MusicVideos": "Videoclipes", "NameInstallFailed": "{0} falha na instalação", "NameSeasonNumber": "Temporada {0}", "NameSeasonUnknown": "Temporada Desconhecida", @@ -118,5 +118,7 @@ "TaskCleanActivityLog": "Limpar registo de atividade", "Undefined": "Indefinido", "Forced": "Forçado", - "Default": "Padrão" + "Default": "Padrão", + "TaskOptimizeDatabaseDescription": "Base de dados compacta e corta espaço livre. A execução desta tarefa depois de digitalizar a biblioteca ou de fazer outras alterações que impliquem modificações na base de dados pode melhorar o desempenho.", + "TaskOptimizeDatabase": "Otimizar base de dados" } From 4a5e8b99a038e3ed41c78b1e06dcc3d6b86cd53a Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Fri, 22 Oct 2021 00:35:14 +0200 Subject: [PATCH 252/278] Extract duplicate code, add test --- .../Tmdb/BoxSets/TmdbBoxSetImageProvider.cs | 38 +------ .../Tmdb/Movies/TmdbMovieImageProvider.cs | 39 +------ .../Tmdb/People/TmdbPersonImageProvider.cs | 17 +-- .../Tmdb/TV/TmdbEpisodeImageProvider.cs | 21 +--- .../Tmdb/TV/TmdbSeasonImageProvider.cs | 21 +--- .../Tmdb/TV/TmdbSeriesImageProvider.cs | 38 +------ .../Plugins/Tmdb/TmdbClientManager.cs | 52 +++++---- .../Plugins/Tmdb/TmdbUtils.cs | 30 ++++++ .../Tmdb/TmdbUtilsTests.cs | 102 +++++++++++++++++- 9 files changed, 177 insertions(+), 181 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs index 35dc36811e7..17082e1f0e6 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs @@ -67,40 +67,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets return Enumerable.Empty(); } - var remoteImages = new List(); + var posters = collection.Images.Posters; + var backdrops = collection.Images.Backdrops; + var remoteImages = new List(posters.Count + backdrops.Count); - for (var i = 0; i < collection.Images.Posters.Count; i++) - { - var poster = collection.Images.Posters[i]; - remoteImages.Add(new RemoteImageInfo - { - Url = _tmdbClientManager.GetPosterUrl(poster.FilePath), - CommunityRating = poster.VoteAverage, - VoteCount = poster.VoteCount, - Width = poster.Width, - Height = poster.Height, - Language = TmdbUtils.AdjustImageLanguage(poster.Iso_639_1, language), - ProviderName = Name, - Type = ImageType.Primary, - RatingType = RatingType.Score - }); - } - - for (var i = 0; i < collection.Images.Backdrops.Count; i++) - { - var backdrop = collection.Images.Backdrops[i]; - remoteImages.Add(new RemoteImageInfo - { - Url = _tmdbClientManager.GetBackdropUrl(backdrop.FilePath), - CommunityRating = backdrop.VoteAverage, - VoteCount = backdrop.VoteCount, - Width = backdrop.Width, - Height = backdrop.Height, - ProviderName = Name, - Type = ImageType.Backdrop, - RatingType = RatingType.Score - }); - } + TmdbUtils.ConvertToRemoteImageInfo(posters, _tmdbClientManager.GetPosterUrl, ImageType.Primary, language, remoteImages); + TmdbUtils.ConvertToRemoteImageInfo(backdrops, _tmdbClientManager.GetBackdropUrl, ImageType.Backdrop, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs index 015eddc1ac6..8d96d495525 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs @@ -13,7 +13,6 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using TMDbLib.Objects.Find; @@ -84,40 +83,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies return Enumerable.Empty(); } - var remoteImages = new List(); + var posters = movie.Images.Posters; + var backdrops = movie.Images.Backdrops; + var remoteImages = new List(posters.Count + backdrops.Count); - for (var i = 0; i < movie.Images.Posters.Count; i++) - { - var poster = movie.Images.Posters[i]; - remoteImages.Add(new RemoteImageInfo - { - Url = _tmdbClientManager.GetPosterUrl(poster.FilePath), - CommunityRating = poster.VoteAverage, - VoteCount = poster.VoteCount, - Width = poster.Width, - Height = poster.Height, - Language = TmdbUtils.AdjustImageLanguage(poster.Iso_639_1, language), - ProviderName = Name, - Type = ImageType.Primary, - RatingType = RatingType.Score - }); - } - - for (var i = 0; i < movie.Images.Backdrops.Count; i++) - { - var backdrop = movie.Images.Backdrops[i]; - remoteImages.Add(new RemoteImageInfo - { - Url = _tmdbClientManager.GetPosterUrl(backdrop.FilePath), - CommunityRating = backdrop.VoteAverage, - VoteCount = backdrop.VoteCount, - Width = backdrop.Width, - Height = backdrop.Height, - ProviderName = Name, - Type = ImageType.Backdrop, - RatingType = RatingType.Score - }); - } + TmdbUtils.ConvertToRemoteImageInfo(posters, _tmdbClientManager.GetPosterUrl, ImageType.Primary, language, remoteImages); + TmdbUtils.ConvertToRemoteImageInfo(backdrops, _tmdbClientManager.GetBackdropUrl, ImageType.Backdrop, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs index 1fc5ccba59f..20f019cd1db 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs @@ -60,21 +60,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People return Enumerable.Empty(); } - var remoteImages = new RemoteImageInfo[personResult.Images.Profiles.Count]; + var profiles = personResult.Images.Profiles; + var remoteImages = new List(profiles.Count); - for (var i = 0; i < personResult.Images.Profiles.Count; i++) - { - var image = personResult.Images.Profiles[i]; - remoteImages[i] = new RemoteImageInfo - { - ProviderName = Name, - Type = ImageType.Primary, - Width = image.Width, - Height = image.Height, - Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, language), - Url = _tmdbClientManager.GetProfileUrl(image.FilePath) - }; - } + TmdbUtils.ConvertToRemoteImageInfo(profiles, _tmdbClientManager.GetProfileUrl, ImageType.Primary, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs index eb75e940502..5cc5e75697e 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs @@ -12,7 +12,6 @@ using System.Threading.Tasks; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; @@ -75,23 +74,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return Enumerable.Empty(); } - var remoteImages = new RemoteImageInfo[stills.Count]; - for (var i = 0; i < stills.Count; i++) - { - var image = stills[i]; - remoteImages[i] = new RemoteImageInfo - { - Url = _tmdbClientManager.GetStillUrl(image.FilePath), - CommunityRating = image.VoteAverage, - VoteCount = image.VoteCount, - Width = image.Width, - Height = image.Height, - Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, language), - ProviderName = Name, - Type = ImageType.Primary, - RatingType = RatingType.Score - }; - } + var remoteImages = new List(stills.Count); + + TmdbUtils.ConvertToRemoteImageInfo(stills, _tmdbClientManager.GetStillUrl, ImageType.Primary, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs index ca44c9bbc31..0909f3e250a 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs @@ -11,7 +11,6 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; @@ -62,23 +61,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return Enumerable.Empty(); } - var remoteImages = new RemoteImageInfo[posters.Count]; - for (var i = 0; i < posters.Count; i++) - { - var image = posters[i]; - remoteImages[i] = new RemoteImageInfo - { - Url = _tmdbClientManager.GetPosterUrl(image.FilePath), - CommunityRating = image.VoteAverage, - VoteCount = image.VoteCount, - Width = image.Width, - Height = image.Height, - Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, language), - ProviderName = Name, - Type = ImageType.Primary, - RatingType = RatingType.Score - }; - } + var remoteImages = new List(posters.Count); + + TmdbUtils.ConvertToRemoteImageInfo(posters, _tmdbClientManager.GetPosterUrl, ImageType.Primary, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs index f3f34037895..37bbea6684f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs @@ -11,7 +11,6 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; @@ -70,41 +69,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var posters = series.Images.Posters; var backdrops = series.Images.Backdrops; + var remoteImages = new List(posters.Count + backdrops.Count); - var remoteImages = new RemoteImageInfo[posters.Count + backdrops.Count]; - - for (var i = 0; i < posters.Count; i++) - { - var poster = posters[i]; - remoteImages[i] = new RemoteImageInfo - { - Url = _tmdbClientManager.GetPosterUrl(poster.FilePath), - CommunityRating = poster.VoteAverage, - VoteCount = poster.VoteCount, - Width = poster.Width, - Height = poster.Height, - Language = TmdbUtils.AdjustImageLanguage(poster.Iso_639_1, language), - ProviderName = Name, - Type = ImageType.Primary, - RatingType = RatingType.Score - }; - } - - for (var i = 0; i < backdrops.Count; i++) - { - var backdrop = series.Images.Backdrops[i]; - remoteImages[posters.Count + i] = new RemoteImageInfo - { - Url = _tmdbClientManager.GetBackdropUrl(backdrop.FilePath), - CommunityRating = backdrop.VoteAverage, - VoteCount = backdrop.VoteCount, - Width = backdrop.Width, - Height = backdrop.Height, - ProviderName = Name, - Type = ImageType.Backdrop, - RatingType = RatingType.Score - }; - } + TmdbUtils.ConvertToRemoteImageInfo(posters, _tmdbClientManager.GetPosterUrl, ImageType.Primary, language, remoteImages); + TmdbUtils.ConvertToRemoteImageInfo(backdrops, _tmdbClientManager.GetBackdropUrl, ImageType.Backdrop, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index 74be4c79338..3c7e3326993 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using System; using System.Collections.Generic; @@ -470,6 +470,22 @@ namespace MediaBrowser.Providers.Plugins.Tmdb return searchResults.Results; } + /// + /// Handles bad path checking and builds the absolute url. + /// + /// The image size to fetch. + /// The relative URL of the image. + /// The absolute URL. + private string GetUrl(string size, string path) + { + if (string.IsNullOrEmpty(path)) + { + return null; + } + + return _tmDbClient.GetImageUrl(size, path).ToString(); + } + /// /// Gets the absolute URL of the poster. /// @@ -477,27 +493,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// The absolute URL. public string GetPosterUrl(string posterPath) { - if (string.IsNullOrEmpty(posterPath)) - { - return null; - } - - return _tmDbClient.GetImageUrl(_tmDbClient.Config.Images.PosterSizes[^1], posterPath).ToString(); + return GetUrl(_tmDbClient.Config.Images.PosterSizes[^1], posterPath); } /// /// Gets the absolute URL of the backdrop image. /// - /// The relative URL of the backdrop image. + /// The relative URL of the backdrop image. /// The absolute URL. - public string GetBackdropUrl(string posterPath) + public string GetBackdropUrl(string backdropPath) { - if (string.IsNullOrEmpty(posterPath)) - { - return null; - } - - return _tmDbClient.GetImageUrl(_tmDbClient.Config.Images.BackdropSizes[^1], posterPath).ToString(); + return GetUrl(_tmDbClient.Config.Images.BackdropSizes[^1], backdropPath); } /// @@ -507,12 +513,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// The absolute URL. public string GetProfileUrl(string actorProfilePath) { - if (string.IsNullOrEmpty(actorProfilePath)) - { - return null; - } - - return _tmDbClient.GetImageUrl(_tmDbClient.Config.Images.ProfileSizes[^1], actorProfilePath).ToString(); + return GetUrl(_tmDbClient.Config.Images.ProfileSizes[^1], actorProfilePath); } /// @@ -522,12 +523,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// The absolute URL. public string GetStillUrl(string filePath) { - if (string.IsNullOrEmpty(filePath)) - { - return null; - } - - return _tmDbClient.GetImageUrl(_tmDbClient.Config.Images.StillSizes[^1], filePath).ToString(); + return GetUrl(_tmDbClient.Config.Images.StillSizes[^1], filePath); } private Task EnsureClientConfigAsync() @@ -542,7 +538,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb GC.SuppressFinalize(this); } -/// + /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index 58ab9f54737..ec4e8373f20 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; using TMDbLib.Objects.General; namespace MediaBrowser.Providers.Plugins.Tmdb @@ -192,5 +194,33 @@ namespace MediaBrowser.Providers.Plugins.Tmdb return newRating.Replace("DE-", "FSK-", StringComparison.OrdinalIgnoreCase); } + + /// + /// Converts s into s. + /// + /// The input images. + /// The relevant GetTypeUrl function to get the absolute url of the image. + /// The type of the image. + /// The requested language. + /// The collection to add the remote images into. + public static void ConvertToRemoteImageInfo(List images, Func imageUrlConverter, ImageType type, string requestLanguage, List results) + { + for (var i = 0; i < images.Count; i++) + { + var image = images[i]; + results.Add(new RemoteImageInfo + { + Url = imageUrlConverter(image.FilePath), + CommunityRating = image.VoteAverage, + VoteCount = image.VoteCount, + Width = image.Width, + Height = image.Height, + Language = AdjustImageLanguage(image.Iso_639_1, requestLanguage), + ProviderName = ProviderName, + Type = type, + RatingType = RatingType.Score + }); + } + } } } diff --git a/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs b/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs index f6a7c676f45..c9a8d11248a 100644 --- a/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs +++ b/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs @@ -1,4 +1,9 @@ -using MediaBrowser.Providers.Plugins.Tmdb; +using System.Collections.Generic; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Plugins.Tmdb; +using TMDbLib.Objects.General; using Xunit; namespace Jellyfin.Providers.Tests.Tmdb @@ -23,5 +28,100 @@ namespace Jellyfin.Providers.Tests.Tmdb { Assert.Equal(expected, TmdbUtils.NormalizeLanguage(input!)); } + + [Theory] + [InlineData(null, null, null)] + [InlineData(null, "en-US", null)] + [InlineData("en", null, "en")] + [InlineData("en", "en-US", "en-US")] + [InlineData("fr-CA", "fr-BE", "fr-CA")] + [InlineData("fr-CA", "fr", "fr-CA")] + [InlineData("de", "en-US", "de")] + public static void AdjustImageLanguage_Valid_Success(string imageLanguage, string requestLanguage, string expected) + { + Assert.Equal(expected, TmdbUtils.AdjustImageLanguage(imageLanguage, requestLanguage)); + } + + private static TheoryData GetConvertedImages() + { + return new TheoryData + { + { + ImageType.Primary, + new () + { + Width = 1, + Height = 1, + AspectRatio = 1, + FilePath = "path 1", + Iso_639_1 = "en", + VoteAverage = 1.2, + VoteCount = 5 + }, + new () + { + Type = ImageType.Primary, + Width = 1, + Height = 1, + Url = "converted path 1", + Language = "en-US", + CommunityRating = 1.2, + VoteCount = 5, + RatingType = RatingType.Score, + ProviderName = TmdbUtils.ProviderName + } + }, + { + ImageType.Backdrop, + new () + { + Width = 4, + Height = 2, + AspectRatio = 2, + FilePath = "path 2", + Iso_639_1 = null, + VoteAverage = 0, + VoteCount = 0 + }, + new () + { + Type = ImageType.Backdrop, + Width = 4, + Height = 2, + Url = "converted path 2", + Language = null, + CommunityRating = 0, + VoteCount = 0, + RatingType = RatingType.Score, + ProviderName = TmdbUtils.ProviderName + } + } + }; + } + + [Theory] + [MemberData(nameof(GetConvertedImages))] + public static void ConvertToRemoteImageInfo_ImageList_ConvertsAll(ImageType type, ImageData input, RemoteImageInfo expected) + { + var images = new List { input }; + string UrlConverter(string s) + => "converted " + s; + var language = "en-US"; + + var results = new List(images.Count); + TmdbUtils.ConvertToRemoteImageInfo(images, UrlConverter, type, language, results); + + Assert.Single(results); + + Assert.Equal(expected.Type, results[0].Type); + Assert.Equal(expected.Width, results[0].Width); + Assert.Equal(expected.Height, results[0].Height); + Assert.Equal(expected.Url, results[0].Url); + Assert.Equal(expected.Language, results[0].Language); + Assert.Equal(expected.CommunityRating, results[0].CommunityRating); + Assert.Equal(expected.VoteCount, results[0].VoteCount); + Assert.Equal(expected.RatingType, results[0].RatingType); + Assert.Equal(expected.ProviderName, results[0].ProviderName); + } } } From 7da6bd905ad23c486da31aa122021e7b8a07d7d7 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Tue, 2 Nov 2021 00:31:59 +0100 Subject: [PATCH 253/278] Fix edge case in multi-image replacing --- MediaBrowser.Providers/Manager/ItemImageProvider.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index f60fce11b86..2c7d43c86d0 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -342,12 +342,12 @@ namespace MediaBrowser.Providers.Manager } minWidth = savedOptions.GetMinWidth(ImageType.Backdrop); - await DownloadMultiImages(item, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); + await DownloadMultiImages(item, ImageType.Backdrop, refreshOptions, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); if (item is IHasScreenshots) { minWidth = savedOptions.GetMinWidth(ImageType.Screenshot); - await DownloadMultiImages(item, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); + await DownloadMultiImages(item, ImageType.Screenshot, refreshOptions, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException) @@ -586,7 +586,7 @@ namespace MediaBrowser.Providers.Manager newIndex); } - private async Task DownloadMultiImages(BaseItem item, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable images, int minWidth, CancellationToken cancellationToken) + private async Task DownloadMultiImages(BaseItem item, ImageType imageType, ImageRefreshOptions refreshOptions, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable images, int minWidth, CancellationToken cancellationToken) { foreach (var image in images.Where(i => i.Type == imageType)) { @@ -626,8 +626,8 @@ namespace MediaBrowser.Providers.Manager break; } - // If there's already an image of the same file size, skip it - if (response.Content.Headers.ContentLength.HasValue) + // If there's already an image of the same file size, skip it unless doing a full refresh + if (response.Content.Headers.ContentLength.HasValue && !refreshOptions.IsReplacingImage(imageType)) { try { From bbf40d6be2b48c37f0c8b19ebfe9d6cc6255e42f Mon Sep 17 00:00:00 2001 From: zehner <37537496+zehnerGIT@users.noreply.github.com> Date: Tue, 2 Nov 2021 09:35:09 +0100 Subject: [PATCH 254/278] Update StreamBuilder.cs LogLevel INFO => DEBUG --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 84d99d550f6..322cc367b6c 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -455,7 +455,7 @@ namespace MediaBrowser.Model.Dlna if (directPlayProfile == null) { - _logger.LogInformation( + _logger.LogDebug( "Profile: {0}, No audio direct play profiles found for {1} with codec {2}", options.Profile.Name ?? "Unknown Profile", item.Path ?? "Unknown path", @@ -682,7 +682,7 @@ namespace MediaBrowser.Model.Dlna bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.Item1); bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamEligibilityResult.Item1); - _logger.LogInformation( + _logger.LogDebug( "Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}", options.Profile.Name ?? "Unknown Profile", item.Path ?? "Unknown path", @@ -1033,7 +1033,7 @@ namespace MediaBrowser.Model.Dlna if (directPlay == null) { - _logger.LogInformation( + _logger.LogDebug( "Container: {Container}, Video: {Video}, Audio: {Audio} cannot be direct played by profile: {Profile} for path: {Path}", container, videoStream?.Codec ?? "no video", @@ -1198,7 +1198,7 @@ namespace MediaBrowser.Model.Dlna private void LogConditionFailure(DeviceProfile profile, string type, ProfileCondition condition, MediaSourceInfo mediaSource) { - _logger.LogInformation( + _logger.LogDebug( "Profile: {0}, DirectPlay=false. Reason={1}.{2} Condition: {3}. ConditionValue: {4}. IsRequired: {5}. Path: {6}", type, profile.Name ?? "Unknown Profile", @@ -1222,7 +1222,7 @@ namespace MediaBrowser.Model.Dlna if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed) { - _logger.LogInformation("Not eligible for {0} due to unsupported subtitles", playMethod); + _logger.LogDebug("Not eligible for {0} due to unsupported subtitles", playMethod); return (false, TranscodeReason.SubtitleCodecNotSupported); } } @@ -1404,7 +1404,7 @@ namespace MediaBrowser.Model.Dlna if (itemBitrate > requestedMaxBitrate) { - _logger.LogInformation( + _logger.LogDebug( "Bitrate exceeds {PlayBackMethod} limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}", playMethod, itemBitrate, From 3d858955b6efe2f91cc77cfbf90a3679a29e1bbd Mon Sep 17 00:00:00 2001 From: zehner <37537496+zehnerGIT@users.noreply.github.com> Date: Tue, 2 Nov 2021 15:11:01 +0100 Subject: [PATCH 255/278] Make tags import from TMDB configurable new settings added --- .../Tmdb/Configuration/PluginConfiguration.cs | 10 ++++++++++ .../Plugins/Tmdb/Configuration/config.html | 10 +++++++--- .../Plugins/Tmdb/TmdbClientManager.cs | 16 ++++++++++++++-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs index 907f0160d63..9ac95f23e99 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs @@ -11,5 +11,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// Gets or sets a value indicating whether include adult content when searching with TMDb. /// public bool IncludeAdult { get; set; } + + /// + /// Gets or sets a value indicating whether tags should be imported for series from TMDb. + /// + public bool ExcludeTagsSeries { get; set; } + + /// + /// Gets or sets a value indicating whether tags should be imported for movies from TMDb. + /// + public bool ExcludeTagsMovies { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html index 6f42549d7d3..bce647f2adf 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html @@ -29,20 +29,24 @@ Dashboard.showLoadingMsg(); ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { document.querySelector('#includeAdult').checked = config.IncludeAdult; + document.querySelector('#excludeTagsSeries').checked = config.ExcludeTagsSeries; + document.querySelector('#excludeTagsMovies').checked = config.ExcludeTagsMovies; Dashboard.hideLoadingMsg(); }); }); - + document.querySelector('.configForm') .addEventListener('submit', function (e) { Dashboard.showLoadingMsg(); - + ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { config.IncludeAdult = document.querySelector('#includeAdult').checked; + config.ExcludeTagsSeries = document.querySelector('#excludeTagsSeries').checked; + config.ExcludeTagsMovies = document.querySelector('#excludeTagsMovies').checked; ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult); }); - + e.preventDefault(); return false; }); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index 74be4c79338..f161a1c12b7 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -57,11 +57,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb await EnsureClientConfigAsync().ConfigureAwait(false); + var extraMethods = MovieMethods.Credits | MovieMethods.Releases | MovieMethods.Images | MovieMethods.Videos; + if (!(Plugin.Instance?.Configuration.ExcludeTagsMovies).GetValueOrDefault()) + { + extraMethods |= MovieMethods.Keywords; + } + movie = await _tmDbClient.GetMovieAsync( tmdbId, TmdbUtils.NormalizeLanguage(language), imageLanguages, - MovieMethods.Credits | MovieMethods.Releases | MovieMethods.Images | MovieMethods.Keywords | MovieMethods.Videos, + extraMethods, cancellationToken).ConfigureAwait(false); if (movie != null) @@ -123,11 +129,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb await EnsureClientConfigAsync().ConfigureAwait(false); + var extraMethods = TvShowMethods.Credits | TvShowMethods.Images | TvShowMethods.ExternalIds | TvShowMethods.Videos | TvShowMethods.ContentRatings | TvShowMethods.EpisodeGroups; + if (!(Plugin.Instance?.Configuration.ExcludeTagsSeries).GetValueOrDefault()) + { + extraMethods |= TvShowMethods.Keywords; + } + series = await _tmDbClient.GetTvShowAsync( tmdbId, language: TmdbUtils.NormalizeLanguage(language), includeImageLanguage: imageLanguages, - extraMethods: TvShowMethods.Credits | TvShowMethods.Images | TvShowMethods.Keywords | TvShowMethods.ExternalIds | TvShowMethods.Videos | TvShowMethods.ContentRatings | TvShowMethods.EpisodeGroups, + extraMethods: extraMethods, cancellationToken: cancellationToken).ConfigureAwait(false); if (series != null) From 104e36f2f9c6440a7547a4c76d80a69d5af84eea Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 2 Nov 2021 16:02:52 +0100 Subject: [PATCH 256/278] Streamline startup code --- .../ApplicationHost.cs | 150 +++++++++--------- Jellyfin.Server/CoreAppHost.cs | 49 +++--- Jellyfin.Server/Program.cs | 41 ++--- MediaBrowser.Common/IApplicationHost.cs | 4 +- .../JellyfinApplicationFactory.cs | 10 +- .../TestAppHost.cs | 12 +- 6 files changed, 123 insertions(+), 143 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 6fd152a42df..512700ac24f 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -147,25 +147,20 @@ namespace Emby.Server.Implementations /// Instance of the interface. /// Instance of the interface. /// The interface. - /// Instance of the interface. - /// Instance of the interface. public ApplicationHost( IServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IStartupOptions options, - IConfiguration startupConfig, - IFileSystem fileSystem, - IServiceCollection serviceCollection) + IConfiguration startupConfig) { ApplicationPaths = applicationPaths; LoggerFactory = loggerFactory; _startupOptions = options; _startupConfig = startupConfig; - _fileSystemManager = fileSystem; - ServiceCollection = serviceCollection; + _fileSystemManager = new ManagedFileSystem(LoggerFactory.CreateLogger(), applicationPaths); Logger = LoggerFactory.CreateLogger(); - fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); + _fileSystemManager.AddShortcutHandler(new MbLinkShortcutHandler(_fileSystemManager)); ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version; ApplicationVersionString = ApplicationVersion.ToString(3); @@ -230,8 +225,6 @@ namespace Emby.Server.Implementations /// protected ILogger Logger { get; } - protected IServiceCollection ServiceCollection { get; } - /// /// Gets the logger factory. /// @@ -521,7 +514,7 @@ namespace Emby.Server.Implementations } /// - public void Init() + public void Init(IServiceCollection serviceCollection) { DiscoverTypes(); @@ -551,128 +544,129 @@ namespace Emby.Server.Implementations CertificatePath = networkConfiguration.CertificatePath; Certificate = GetCertificate(CertificatePath, networkConfiguration.CertificatePassword); - RegisterServices(); + RegisterServices(serviceCollection); - _pluginManager.RegisterServices(ServiceCollection); + _pluginManager.RegisterServices(serviceCollection); } /// /// Registers services/resources with the service collection that will be available via DI. /// - protected virtual void RegisterServices() + /// Instance of the interface. + protected virtual void RegisterServices(IServiceCollection serviceCollection) { - ServiceCollection.AddSingleton(_startupOptions); + serviceCollection.AddSingleton(_startupOptions); - ServiceCollection.AddMemoryCache(); + serviceCollection.AddMemoryCache(); - ServiceCollection.AddSingleton(ConfigurationManager); - ServiceCollection.AddSingleton(ConfigurationManager); - ServiceCollection.AddSingleton(this); - ServiceCollection.AddSingleton(_pluginManager); - ServiceCollection.AddSingleton(ApplicationPaths); + serviceCollection.AddSingleton(ConfigurationManager); + serviceCollection.AddSingleton(ConfigurationManager); + serviceCollection.AddSingleton(this); + serviceCollection.AddSingleton(_pluginManager); + serviceCollection.AddSingleton(ApplicationPaths); - ServiceCollection.AddSingleton(_fileSystemManager); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(_fileSystemManager); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(NetManager); + serviceCollection.AddSingleton(NetManager); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(_xmlSerializer); + serviceCollection.AddSingleton(_xmlSerializer); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(this); - ServiceCollection.AddSingleton(ApplicationPaths); + serviceCollection.AddSingleton(this); + serviceCollection.AddSingleton(ApplicationPaths); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); // TODO: Refactor to eliminate the circular dependencies here so that Lazy isn't required - ServiceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); - ServiceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); - ServiceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); - ServiceCollection.AddSingleton(); + serviceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); + serviceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); + serviceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); // TODO: Refactor to eliminate the circular dependency here so that Lazy isn't required - ServiceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); - ServiceCollection.AddSingleton(); + serviceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddScoped(); + serviceCollection.AddScoped(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddScoped(); - ServiceCollection.AddScoped(); - ServiceCollection.AddScoped(); + serviceCollection.AddSingleton(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); } /// diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 21bd9ba0114..67e50b92d9a 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -22,7 +22,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Model.Activity; -using MediaBrowser.Model.IO; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -42,67 +41,61 @@ namespace Jellyfin.Server /// The to be used by the . /// The to be used by the . /// The to be used by the . - /// The to be used by the . - /// The to be used by the . public CoreAppHost( IServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IStartupOptions options, - IConfiguration startupConfig, - IFileSystem fileSystem, - IServiceCollection collection) + IConfiguration startupConfig) : base( applicationPaths, loggerFactory, options, - startupConfig, - fileSystem, - collection) + startupConfig) { } /// - protected override void RegisterServices() + protected override void RegisterServices(IServiceCollection serviceCollection) { // Register an image encoder bool useSkiaEncoder = SkiaEncoder.IsNativeLibAvailable(); Type imageEncoderType = useSkiaEncoder ? typeof(SkiaEncoder) : typeof(NullImageEncoder); - ServiceCollection.AddSingleton(typeof(IImageEncoder), imageEncoderType); + serviceCollection.AddSingleton(typeof(IImageEncoder), imageEncoderType); // Log a warning if the Skia encoder could not be used if (!useSkiaEncoder) { - Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}."); + Logger.LogWarning("Skia not available. Will fallback to {ImageEncoder}.", nameof(NullImageEncoder)); } - ServiceCollection.AddDbContextPool( + serviceCollection.AddDbContextPool( options => options .UseLoggerFactory(LoggerFactory) .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}")); - ServiceCollection.AddEventServices(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddEventServices(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); // TODO search the assemblies instead of adding them manually? - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - ServiceCollection.AddScoped(); + serviceCollection.AddScoped(); - base.RegisterServices(); + base.RegisterServices(serviceCollection); } /// diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 45699f3aff2..5f848be9e1e 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -10,7 +10,6 @@ using System.Threading; using System.Threading.Tasks; using CommandLine; using Emby.Server.Implementations; -using Emby.Server.Implementations.IO; using Jellyfin.Server.Implementations; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; @@ -157,34 +156,36 @@ namespace Jellyfin.Server ApplicationHost.LogEnvironmentInfo(_logger, appPaths); + // If hosting the web client, validate the client content path + if (startupConfig.HostWebClient()) + { + string? webContentPath = appPaths.WebPath; + if (!Directory.Exists(webContentPath) || !Directory.EnumerateFiles(webContentPath).Any()) + { + _logger.LogError( + "The server is expected to host the web client, but the provided content directory is either " + + "invalid or empty: {WebContentPath}. If you do not want to host the web client with the " + + "server, you may set the '--nowebclient' command line flag, or set" + + "'{ConfigKey}=false' in your config settings.", + webContentPath, + ConfigurationExtensions.HostWebClientKey); + Environment.ExitCode = 1; + return; + } + } + PerformStaticInitialization(); - var serviceCollection = new ServiceCollection(); var appHost = new CoreAppHost( appPaths, _loggerFactory, options, - startupConfig, - new ManagedFileSystem(_loggerFactory.CreateLogger(), appPaths), - serviceCollection); + startupConfig); try { - // If hosting the web client, validate the client content path - if (startupConfig.HostWebClient()) - { - string? webContentPath = appHost.ConfigurationManager.ApplicationPaths.WebPath; - if (!Directory.Exists(webContentPath) || Directory.GetFiles(webContentPath).Length == 0) - { - throw new InvalidOperationException( - "The server is expected to host the web client, but the provided content directory is either " + - $"invalid or empty: {webContentPath}. If you do not want to host the web client with the " + - "server, you may set the '--nowebclient' command line flag, or set" + - $"'{ConfigurationExtensions.HostWebClientKey}=false' in your config settings."); - } - } - - appHost.Init(); + var serviceCollection = new ServiceCollection(); + appHost.Init(serviceCollection); var webHost = new WebHostBuilder().ConfigureWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build(); diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 192a7761151..e49ab41f4ff 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; namespace MediaBrowser.Common { @@ -137,7 +138,8 @@ namespace MediaBrowser.Common /// /// Initializes this instance. /// - void Init(); + /// Instance of the interface. + void Init(IServiceCollection serviceCollection); /// /// Creates the instance. diff --git a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs index 976e19d4680..3d34a18e7f5 100644 --- a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs @@ -3,7 +3,6 @@ using System.Collections.Concurrent; using System.IO; using System.Threading; using Emby.Server.Implementations; -using Emby.Server.Implementations.IO; using MediaBrowser.Common; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; @@ -67,7 +66,7 @@ namespace Jellyfin.Server.Integration.Tests var startupConfig = Program.CreateAppConfiguration(commandLineOpts, appPaths); ILoggerFactory loggerFactory = new SerilogLoggerFactory(); - var serviceCollection = new ServiceCollection(); + _disposableComponents.Add(loggerFactory); // Create the app host and initialize it @@ -75,11 +74,10 @@ namespace Jellyfin.Server.Integration.Tests appPaths, loggerFactory, commandLineOpts, - new ConfigurationBuilder().Build(), - new ManagedFileSystem(loggerFactory.CreateLogger(), appPaths), - serviceCollection); + new ConfigurationBuilder().Build()); _disposableComponents.Add(appHost); - appHost.Init(); + var serviceCollection = new ServiceCollection(); + appHost.Init(serviceCollection); // Configure the web host builder Program.ConfigureWebHostBuilder(builder, appHost, serviceCollection, commandLineOpts, startupConfig, appPaths); diff --git a/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs index 0a463cfa392..bf74efa09e4 100644 --- a/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs +++ b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs @@ -2,9 +2,7 @@ using System.Collections.Generic; using System.Reflection; using Emby.Server.Implementations; using MediaBrowser.Controller; -using MediaBrowser.Model.IO; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Integration.Tests @@ -21,22 +19,16 @@ namespace Jellyfin.Server.Integration.Tests /// The to be used by the . /// The to be used by the . /// The to be used by the . - /// The to be used by the . - /// The to be used by the . public TestAppHost( IServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IStartupOptions options, - IConfiguration startup, - IFileSystem fileSystem, - IServiceCollection collection) + IConfiguration startup) : base( applicationPaths, loggerFactory, options, - startup, - fileSystem, - collection) + startup) { } From 7fcf01235c2360ec64cad685df7f155ef3dee69a Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Tue, 2 Nov 2021 16:16:06 +0100 Subject: [PATCH 257/278] Change RemoveImages to array, improve download test --- MediaBrowser.Controller/Entities/BaseItem.cs | 2 +- .../Manager/ItemImageProvider.cs | 20 +++++----- .../Manager/ItemImageProviderTests.cs | 39 ++++++++++++------- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 7dd8e310ed2..02ee97b23fe 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2345,7 +2345,7 @@ namespace MediaBrowser.Controller.Entities RemoveImages(new List { image }); } - public void RemoveImages(List deletedImages) + public void RemoveImages(IEnumerable deletedImages) { ImageInfos = ImageInfos.Except(deletedImages).ToArray(); } diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 2c7d43c86d0..8d5795f8e18 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -103,16 +103,16 @@ namespace MediaBrowser.Providers.Manager ImageRefreshOptions refreshOptions, CancellationToken cancellationToken) { - List oldBackdropImages = new List(); + var oldBackdropImages = Array.Empty(); if (refreshOptions.IsReplacingImage(ImageType.Backdrop)) { - oldBackdropImages = item.GetImages(ImageType.Backdrop).ToList(); + oldBackdropImages = item.GetImages(ImageType.Backdrop).ToArray(); } - List oldScreenshotImages = new List(); + var oldScreenshotImages = Array.Empty(); if (refreshOptions.IsReplacingImage(ImageType.Screenshot)) { - oldScreenshotImages = item.GetImages(ImageType.Screenshot).ToList(); + oldScreenshotImages = item.GetImages(ImageType.Screenshot).ToArray(); } var result = new RefreshResult { UpdateType = ItemUpdateType.None }; @@ -121,8 +121,8 @@ namespace MediaBrowser.Providers.Manager var typeOptions = libraryOptions.GetTypeOptions(typeName) ?? new TypeOptions { Type = typeName }; // track library limits, adding buffer to allow lazy replacing of current images - var backdropLimit = typeOptions.GetLimit(ImageType.Backdrop) + oldBackdropImages.Count; - var screenshotLimit = typeOptions.GetLimit(ImageType.Screenshot) + oldScreenshotImages.Count; + var backdropLimit = typeOptions.GetLimit(ImageType.Backdrop) + oldBackdropImages.Length; + var screenshotLimit = typeOptions.GetLimit(ImageType.Screenshot) + oldScreenshotImages.Length; var downloadedImages = new List(); foreach (var provider in providers) @@ -140,12 +140,12 @@ namespace MediaBrowser.Providers.Manager } // only delete existing multi-images if new ones were added - if (oldBackdropImages.Count > 0 && oldBackdropImages.Count < item.GetImages(ImageType.Backdrop).Count()) + if (oldBackdropImages.Length > 0 && oldBackdropImages.Length < item.GetImages(ImageType.Backdrop).Count()) { PruneImages(item, oldBackdropImages); } - if (oldScreenshotImages.Count > 0 && oldScreenshotImages.Count < item.GetImages(ImageType.Screenshot).Count()) + if (oldScreenshotImages.Length > 0 && oldScreenshotImages.Length < item.GetImages(ImageType.Screenshot).Count()) { PruneImages(item, oldScreenshotImages); } @@ -366,9 +366,9 @@ namespace MediaBrowser.Providers.Manager return options.IsEnabled(type); } - private void PruneImages(BaseItem item, List images) + private void PruneImages(BaseItem item, ItemImageInfo[] images) { - for (var i = 0; i < images.Count; i++) + for (var i = 0; i < images.Length; i++) { var image = images[i]; diff --git a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs index b5efd8f0133..54f2cb71bf6 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs @@ -409,21 +409,23 @@ namespace Jellyfin.Providers.Tests.Manager } [Theory] - [MemberData(nameof(GetImageTypesWithCount))] - public async void RefreshImages_EmptyNonStubItemPopulatedProviderRemote_DownloadsImages(ImageType imageType, int imageCount) + [InlineData(ImageType.Primary, 0, false)] // singular type only fetches if type is missing from item, no caching + [InlineData(ImageType.Backdrop, 0, false)] // empty item, no cache to check + [InlineData(ImageType.Backdrop, 1, false)] // populated item, cached so no download + [InlineData(ImageType.Backdrop, 1, true)] // populated item, forced to download + public async void RefreshImages_NonStubItemPopulatedProviderRemote_DownloadsIfNecessary(ImageType imageType, int initialImageCount, bool fullRefresh) { - // Has to exist for querying DateModified time on file, results stored but not checked so not populating - BaseItem.FileSystem ??= Mock.Of(); + var targetImageCount = 1; // Set path and media source manager so images will be downloaded (EnableImageStub will return false) - var item = new MovieWithScreenshots - { - Path = "non-empty path" - }; + var item = GetItemWithImages(imageType, initialImageCount, false); + item.Path = "non-empty path"; BaseItem.MediaSourceManager = Mock.Of(); - var libraryOptions = GetLibraryOptions(item, imageType, imageCount); + // seek 2 so it won't short-circuit out of downloading when populated + var libraryOptions = GetLibraryOptions(item, imageType, 2); + var content = "Content"; var remoteProvider = new Mock(MockBehavior.Strict); remoteProvider.Setup(rp => rp.Name).Returns("MockRemoteProvider"); remoteProvider.Setup(rp => rp.GetSupportedImages(item)) @@ -433,13 +435,19 @@ namespace Jellyfin.Providers.Tests.Manager { ReasonPhrase = url, StatusCode = HttpStatusCode.OK, - Content = new StringContent("Content", Encoding.UTF8, "image/jpeg") + Content = new StringContent(content, Encoding.UTF8, "image/jpeg") }); - var refreshOptions = new ImageRefreshOptions(null); + var refreshOptions = fullRefresh + ? new ImageRefreshOptions(null) + { + ImageRefreshMode = MetadataRefreshMode.FullRefresh, + ReplaceAllImages = true + } + : new ImageRefreshOptions(null); var remoteInfo = new List(); - for (int i = 0; i < imageCount; i++) + for (int i = 0; i < targetImageCount; i++) { remoteInfo.Add(new RemoteImageInfo { @@ -457,13 +465,14 @@ namespace Jellyfin.Providers.Tests.Manager callbackItem.SetImagePath(callbackType, callbackItem.AllowsMultipleImages(callbackType) ? callbackItem.GetImages(callbackType).Count() : 0, new FileSystemMetadata())) .Returns(Task.CompletedTask); var fileSystem = new Mock(); + // match reported file size to image content length - condition for skipping already downloaded multi-images fileSystem.Setup(fs => fs.GetFileInfo(It.IsAny())) - .Returns(new FileSystemMetadata { Length = 1 }); + .Returns(new FileSystemMetadata { Length = content.Length }); var itemImageProvider = GetItemImageProvider(providerManager.Object, fileSystem); var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { remoteProvider.Object }, refreshOptions, CancellationToken.None); - Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); - Assert.Equal(imageCount, item.GetImages(imageType).Count()); + Assert.Equal(initialImageCount == 0 || fullRefresh, result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); + Assert.Equal(targetImageCount, item.GetImages(imageType).Count()); } [Theory] From 63eeb73608d273bd9d2f52c2ac5a66cd0195c8eb Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Tue, 2 Nov 2021 18:55:07 +0100 Subject: [PATCH 258/278] Update openapi workflow to use .NET 6 --- .github/workflows/openapi.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml index 701e38fb2ba..6e370819af9 100644 --- a/.github/workflows/openapi.yml +++ b/.github/workflows/openapi.yml @@ -15,7 +15,8 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: '5.0.x' + dotnet-version: '6.0.x' + include-prerelease: true - name: Generate openapi.json run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests" - name: Upload openapi.json @@ -24,7 +25,7 @@ jobs: name: openapi-head retention-days: 14 if-no-files-found: error - path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net5.0/openapi.json + path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net6.0/openapi.json openapi-base: name: OpenAPI - BASE @@ -38,7 +39,8 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: '5.0.x' + dotnet-version: '6.0.x' + include-prerelease: true - name: Generate openapi.json run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests" - name: Upload openapi.json @@ -47,7 +49,7 @@ jobs: name: openapi-base retention-days: 14 if-no-files-found: error - path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net5.0/openapi.json + path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net6.0/openapi.json openapi-diff: name: OpenAPI - Difference From 2b283d249fc040b62d8d4bcd4623896b4aed3d15 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Tue, 2 Nov 2021 21:12:13 +0100 Subject: [PATCH 259/278] Switch to method per image conversion --- .../Tmdb/BoxSets/TmdbBoxSetImageProvider.cs | 4 +- .../Tmdb/Movies/TmdbMovieImageProvider.cs | 4 +- .../Tmdb/People/TmdbPersonImageProvider.cs | 2 +- .../Tmdb/TV/TmdbEpisodeImageProvider.cs | 2 +- .../Tmdb/TV/TmdbSeasonImageProvider.cs | 2 +- .../Tmdb/TV/TmdbSeriesImageProvider.cs | 4 +- .../Plugins/Tmdb/TmdbUtils.cs | 50 ++++++++++- .../Tmdb/TmdbUtilsTests.cs | 89 +------------------ 8 files changed, 59 insertions(+), 98 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs index 17082e1f0e6..17f3e635ff6 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs @@ -71,8 +71,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets var backdrops = collection.Images.Backdrops; var remoteImages = new List(posters.Count + backdrops.Count); - TmdbUtils.ConvertToRemoteImageInfo(posters, _tmdbClientManager.GetPosterUrl, ImageType.Primary, language, remoteImages); - TmdbUtils.ConvertToRemoteImageInfo(backdrops, _tmdbClientManager.GetBackdropUrl, ImageType.Backdrop, language, remoteImages); + TmdbUtils.ConvertPostersToRemoteImageInfo(posters, _tmdbClientManager, language, remoteImages); + TmdbUtils.ConvertBackdropsToRemoteImageInfo(backdrops, _tmdbClientManager, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs index 8d96d495525..4336efee45a 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs @@ -87,8 +87,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies var backdrops = movie.Images.Backdrops; var remoteImages = new List(posters.Count + backdrops.Count); - TmdbUtils.ConvertToRemoteImageInfo(posters, _tmdbClientManager.GetPosterUrl, ImageType.Primary, language, remoteImages); - TmdbUtils.ConvertToRemoteImageInfo(backdrops, _tmdbClientManager.GetBackdropUrl, ImageType.Backdrop, language, remoteImages); + TmdbUtils.ConvertPostersToRemoteImageInfo(posters, _tmdbClientManager, language, remoteImages); + TmdbUtils.ConvertBackdropsToRemoteImageInfo(backdrops, _tmdbClientManager, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs index 20f019cd1db..cad62eca3cb 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs @@ -63,7 +63,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People var profiles = personResult.Images.Profiles; var remoteImages = new List(profiles.Count); - TmdbUtils.ConvertToRemoteImageInfo(profiles, _tmdbClientManager.GetProfileUrl, ImageType.Primary, language, remoteImages); + TmdbUtils.ConvertProfilesToRemoteImageInfo(profiles, _tmdbClientManager, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs index 5cc5e75697e..b7dda9b5026 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs @@ -76,7 +76,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var remoteImages = new List(stills.Count); - TmdbUtils.ConvertToRemoteImageInfo(stills, _tmdbClientManager.GetStillUrl, ImageType.Primary, language, remoteImages); + TmdbUtils.ConvertStillsToRemoteImageInfo(stills, _tmdbClientManager, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs index 0909f3e250a..90b324a4fe4 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs @@ -63,7 +63,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var remoteImages = new List(posters.Count); - TmdbUtils.ConvertToRemoteImageInfo(posters, _tmdbClientManager.GetPosterUrl, ImageType.Primary, language, remoteImages); + TmdbUtils.ConvertPostersToRemoteImageInfo(posters, _tmdbClientManager, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs index 37bbea6684f..9f2821c404e 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs @@ -71,8 +71,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var backdrops = series.Images.Backdrops; var remoteImages = new List(posters.Count + backdrops.Count); - TmdbUtils.ConvertToRemoteImageInfo(posters, _tmdbClientManager.GetPosterUrl, ImageType.Primary, language, remoteImages); - TmdbUtils.ConvertToRemoteImageInfo(backdrops, _tmdbClientManager.GetBackdropUrl, ImageType.Backdrop, language, remoteImages); + TmdbUtils.ConvertPostersToRemoteImageInfo(posters, _tmdbClientManager, language, remoteImages); + TmdbUtils.ConvertBackdropsToRemoteImageInfo(backdrops, _tmdbClientManager, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index ec4e8373f20..3cdab601ac4 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -195,6 +195,54 @@ namespace MediaBrowser.Providers.Plugins.Tmdb return newRating.Replace("DE-", "FSK-", StringComparison.OrdinalIgnoreCase); } + /// + /// Converts poster s into s. + /// + /// The input images. + /// The client manager to use for resolving image urls. + /// The requested language. + /// The collection to add the remote images into. + public static void ConvertPostersToRemoteImageInfo(List images, TmdbClientManager tmdbClientManager, string requestLanguage, List results) + { + ConvertToRemoteImageInfo(images, tmdbClientManager.GetPosterUrl, ImageType.Primary, requestLanguage, results); + } + + /// + /// Converts backdrop s into s. + /// + /// The input images. + /// The client manager to use for resolving image urls. + /// The requested language. + /// The collection to add the remote images into. + public static void ConvertBackdropsToRemoteImageInfo(List images, TmdbClientManager tmdbClientManager, string requestLanguage, List results) + { + ConvertToRemoteImageInfo(images, tmdbClientManager.GetBackdropUrl, ImageType.Backdrop, requestLanguage, results); + } + + /// + /// Converts profile s into s. + /// + /// The input images. + /// The client manager to use for resolving image urls. + /// The requested language. + /// The collection to add the remote images into. + public static void ConvertProfilesToRemoteImageInfo(List images, TmdbClientManager tmdbClientManager, string requestLanguage, List results) + { + ConvertToRemoteImageInfo(images, tmdbClientManager.GetProfileUrl, ImageType.Primary, requestLanguage, results); + } + + /// + /// Converts still s into s. + /// + /// The input images. + /// The client manager to use for resolving image urls. + /// The requested language. + /// The collection to add the remote images into. + public static void ConvertStillsToRemoteImageInfo(List images, TmdbClientManager tmdbClientManager, string requestLanguage, List results) + { + ConvertToRemoteImageInfo(images, tmdbClientManager.GetStillUrl, ImageType.Primary, requestLanguage, results); + } + /// /// Converts s into s. /// @@ -203,7 +251,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// The type of the image. /// The requested language. /// The collection to add the remote images into. - public static void ConvertToRemoteImageInfo(List images, Func imageUrlConverter, ImageType type, string requestLanguage, List results) + private static void ConvertToRemoteImageInfo(List images, Func imageUrlConverter, ImageType type, string requestLanguage, List results) { for (var i = 0; i < images.Count; i++) { diff --git a/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs b/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs index c9a8d11248a..efd2d9553fc 100644 --- a/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs +++ b/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs @@ -1,9 +1,4 @@ -using System.Collections.Generic; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; -using MediaBrowser.Providers.Plugins.Tmdb; -using TMDbLib.Objects.General; +using MediaBrowser.Providers.Plugins.Tmdb; using Xunit; namespace Jellyfin.Providers.Tests.Tmdb @@ -41,87 +36,5 @@ namespace Jellyfin.Providers.Tests.Tmdb { Assert.Equal(expected, TmdbUtils.AdjustImageLanguage(imageLanguage, requestLanguage)); } - - private static TheoryData GetConvertedImages() - { - return new TheoryData - { - { - ImageType.Primary, - new () - { - Width = 1, - Height = 1, - AspectRatio = 1, - FilePath = "path 1", - Iso_639_1 = "en", - VoteAverage = 1.2, - VoteCount = 5 - }, - new () - { - Type = ImageType.Primary, - Width = 1, - Height = 1, - Url = "converted path 1", - Language = "en-US", - CommunityRating = 1.2, - VoteCount = 5, - RatingType = RatingType.Score, - ProviderName = TmdbUtils.ProviderName - } - }, - { - ImageType.Backdrop, - new () - { - Width = 4, - Height = 2, - AspectRatio = 2, - FilePath = "path 2", - Iso_639_1 = null, - VoteAverage = 0, - VoteCount = 0 - }, - new () - { - Type = ImageType.Backdrop, - Width = 4, - Height = 2, - Url = "converted path 2", - Language = null, - CommunityRating = 0, - VoteCount = 0, - RatingType = RatingType.Score, - ProviderName = TmdbUtils.ProviderName - } - } - }; - } - - [Theory] - [MemberData(nameof(GetConvertedImages))] - public static void ConvertToRemoteImageInfo_ImageList_ConvertsAll(ImageType type, ImageData input, RemoteImageInfo expected) - { - var images = new List { input }; - string UrlConverter(string s) - => "converted " + s; - var language = "en-US"; - - var results = new List(images.Count); - TmdbUtils.ConvertToRemoteImageInfo(images, UrlConverter, type, language, results); - - Assert.Single(results); - - Assert.Equal(expected.Type, results[0].Type); - Assert.Equal(expected.Width, results[0].Width); - Assert.Equal(expected.Height, results[0].Height); - Assert.Equal(expected.Url, results[0].Url); - Assert.Equal(expected.Language, results[0].Language); - Assert.Equal(expected.CommunityRating, results[0].CommunityRating); - Assert.Equal(expected.VoteCount, results[0].VoteCount); - Assert.Equal(expected.RatingType, results[0].RatingType); - Assert.Equal(expected.ProviderName, results[0].ProviderName); - } } } From 149c77d9b180d2a64f4d9acf392e26611129f82d Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Tue, 2 Nov 2021 22:46:53 +0100 Subject: [PATCH 260/278] Remove commented theory data, merge tests --- .../Manager/ItemImageProviderTests.cs | 302 +++++++----------- 1 file changed, 108 insertions(+), 194 deletions(-) diff --git a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs index 54f2cb71bf6..6d65ba2d7a8 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs @@ -50,35 +50,14 @@ namespace Jellyfin.Providers.Tests.Manager private static TheoryData GetImageTypesWithCount() { - var theoryTypes = new TheoryData(); - - // shotgun approach; overkill for frequent runs - // foreach (var imageType in (ImageType[])Enum.GetValues(typeof(ImageType))) - // { - // switch (imageType) - // { - // case ImageType.Chapter: - // case ImageType.Profile: - // // skip types that can't be set using BaseItem.SetImagePath or otherwise don't apply to BaseItem - // break; - // case ImageType.Backdrop: - // case ImageType.Screenshot: - // // for types that support multiple test with 1 and with more than 1 - // theoryTypes.Add(imageType, 1); - // theoryTypes.Add(imageType, 2); - // break; - // default: - // // for singular types just test with 1 - // theoryTypes.Add(imageType, 1); - // break; - // } - // } - - // specific test cases that hit different handling - theoryTypes.Add(ImageType.Primary, 1); - theoryTypes.Add(ImageType.Backdrop, 1); - theoryTypes.Add(ImageType.Backdrop, 2); - theoryTypes.Add(ImageType.Screenshot, 1); + var theoryTypes = new TheoryData + { + // minimal test cases that hit different handling + { ImageType.Primary, 1 }, + { ImageType.Backdrop, 1 }, + { ImageType.Backdrop, 2 }, + { ImageType.Screenshot, 1 } + }; return theoryTypes; } @@ -228,125 +207,23 @@ namespace Jellyfin.Providers.Tests.Manager } [Theory] - [MemberData(nameof(GetImageTypesWithCount))] - public async void RefreshImages_PopulatedItemPopulatedProviderDynamic_NoChange(ImageType imageType, int imageCount) - { - var item = GetItemWithImages(imageType, imageCount, true); - - var libraryOptions = GetLibraryOptions(item, imageType, imageCount); - - var dynamicProvider = new Mock(MockBehavior.Strict); - dynamicProvider.Setup(rp => rp.Name).Returns("MockDynamicProvider"); - dynamicProvider.Setup(rp => rp.GetSupportedImages(item)) - .Returns(new[] { imageType }); - - var refreshOptions = new ImageRefreshOptions(null); - - var providerManager = new Mock(MockBehavior.Strict); - providerManager.Setup(pm => pm.SaveImage(item, It.IsAny(), It.IsAny(), imageType, null, It.IsAny())) - .Callback((callbackItem, _, _, callbackType, _, _) => callbackItem.SetImagePath(callbackType, 0, new FileSystemMetadata())) - .Returns(Task.CompletedTask); - var itemImageProvider = GetItemImageProvider(providerManager.Object, null); - var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { dynamicProvider.Object }, refreshOptions, CancellationToken.None); - - Assert.False(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); - Assert.Equal(imageCount, item.GetImages(imageType).Count()); - } - - [Theory] - [MemberData(nameof(GetImageTypesWithCount))] - public async void RefreshImages_EmptyItemPopulatedProviderDynamicWithPath_AddsImages(ImageType imageType, int imageCount) - { - // Has to exist for querying DateModified time on file, results stored but not checked so not populating - BaseItem.FileSystem = Mock.Of(); - - var item = new MovieWithScreenshots(); - - var libraryOptions = GetLibraryOptions(item, imageType, imageCount); - - // Path must exist: is read in as a stream by AsyncFile.OpenRead - var imageResponse = new DynamicImageResponse - { - HasImage = true, - Format = ImageFormat.Jpg, - Path = string.Format(CultureInfo.InvariantCulture, TestDataImagePath, 0), - Protocol = MediaProtocol.File - }; - - var dynamicProvider = new Mock(MockBehavior.Strict); - dynamicProvider.Setup(rp => rp.Name).Returns("MockDynamicProvider"); - dynamicProvider.Setup(rp => rp.GetSupportedImages(item)) - .Returns(new[] { imageType }); - dynamicProvider.Setup(rp => rp.GetImage(item, imageType, It.IsAny())) - .ReturnsAsync(imageResponse); - - var refreshOptions = new ImageRefreshOptions(null); - - var providerManager = new Mock(MockBehavior.Strict); - providerManager.Setup(pm => pm.SaveImage(item, It.IsAny(), It.IsAny(), imageType, null, It.IsAny())) - .Callback((callbackItem, _, _, callbackType, _, _) => callbackItem.SetImagePath(callbackType, 0, new FileSystemMetadata())) - .Returns(Task.CompletedTask); - var itemImageProvider = GetItemImageProvider(providerManager.Object, null); - var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { dynamicProvider.Object }, refreshOptions, CancellationToken.None); - - Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); - // dynamic provider unable to return multiple images - Assert.Single(item.GetImages(imageType)); - } - - [Theory] - [MemberData(nameof(GetImageTypesWithCount))] - public async void RefreshImages_EmptyItemPopulatedProviderDynamicWithoutPath_AddsImages(ImageType imageType, int imageCount) - { - // Has to exist for querying DateModified time on file, results stored but not checked so not populating - BaseItem.FileSystem = Mock.Of(); - - var item = new MovieWithScreenshots(); - - var libraryOptions = GetLibraryOptions(item, imageType, imageCount); - - var imageResponse = new DynamicImageResponse - { - HasImage = true, - Format = ImageFormat.Jpg, - Protocol = MediaProtocol.File - }; - - var dynamicProvider = new Mock(MockBehavior.Strict); - dynamicProvider.Setup(rp => rp.Name).Returns("MockDynamicProvider"); - dynamicProvider.Setup(rp => rp.GetSupportedImages(item)) - .Returns(new[] { imageType }); - dynamicProvider.Setup(rp => rp.GetImage(item, imageType, It.IsAny())) - .ReturnsAsync(imageResponse); - - var refreshOptions = new ImageRefreshOptions(null); - - var providerManager = new Mock(MockBehavior.Strict); - providerManager.Setup(pm => pm.SaveImage(item, It.IsAny(), It.IsAny(), imageType, null, It.IsAny())) - .Callback((callbackItem, _, _, callbackType, _, _) => callbackItem.SetImagePath(callbackType, 0, new FileSystemMetadata())) - .Returns(Task.CompletedTask); - var itemImageProvider = GetItemImageProvider(providerManager.Object, null); - var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { dynamicProvider.Object }, refreshOptions, CancellationToken.None); - - Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); - // dynamic provider unable to return multiple images - Assert.Single(item.GetImages(imageType)); - } - - [Theory] - [MemberData(nameof(GetImageTypesWithCount))] - public async void RefreshImages_PopulatedItemPopulatedProviderDynamicFullRefresh_UpdatesImages(ImageType imageType, int imageCount) + [InlineData(ImageType.Primary, 1, false)] + [InlineData(ImageType.Backdrop, 2, false)] + [InlineData(ImageType.Screenshot, 2, false)] + [InlineData(ImageType.Primary, 1, true)] + [InlineData(ImageType.Backdrop, 2, true)] + [InlineData(ImageType.Screenshot, 2, true)] + public async void RefreshImages_PopulatedItemPopulatedProviderDynamic_UpdatesImagesIfForced(ImageType imageType, int imageCount, bool forceRefresh) { var item = GetItemWithImages(imageType, imageCount, false); var libraryOptions = GetLibraryOptions(item, imageType, imageCount); - var expectedPath = "dynamic response path url"; var imageResponse = new DynamicImageResponse { HasImage = true, Format = ImageFormat.Jpg, - Path = expectedPath, + Path = "url path", Protocol = MediaProtocol.Http }; @@ -357,24 +234,89 @@ namespace Jellyfin.Providers.Tests.Manager dynamicProvider.Setup(rp => rp.GetImage(item, imageType, It.IsAny())) .ReturnsAsync(imageResponse); - var refreshOptions = new ImageRefreshOptions(null) - { - ImageRefreshMode = MetadataRefreshMode.FullRefresh, - ReplaceAllImages = true - }; + var refreshOptions = forceRefresh + ? new ImageRefreshOptions(null) + { + ImageRefreshMode = MetadataRefreshMode.FullRefresh, ReplaceAllImages = true + } + : new ImageRefreshOptions(null); var itemImageProvider = GetItemImageProvider(null, new Mock()); var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { dynamicProvider.Object }, refreshOptions, CancellationToken.None); + Assert.Equal(forceRefresh, result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); + if (forceRefresh) + { + // replaces multi-types + Assert.Single(item.GetImages(imageType)); + } + else + { + // adds to multi-types if room + Assert.Equal(imageCount, item.GetImages(imageType).Count()); + } + } + + [Theory] + [InlineData(ImageType.Primary, 1, true, MediaProtocol.Http)] + [InlineData(ImageType.Backdrop, 2, true, MediaProtocol.Http)] + [InlineData(ImageType.Primary, 1, true, MediaProtocol.File)] + [InlineData(ImageType.Backdrop, 2, true, MediaProtocol.File)] + [InlineData(ImageType.Primary, 1, false, MediaProtocol.File)] + [InlineData(ImageType.Backdrop, 2, false, MediaProtocol.File)] + public async void RefreshImages_EmptyItemPopulatedProviderDynamic_AddsImages(ImageType imageType, int imageCount, bool responseHasPath, MediaProtocol protocol) + { + // Has to exist for querying DateModified time on file, results stored but not checked so not populating + BaseItem.FileSystem = Mock.Of(); + + var item = new MovieWithScreenshots(); + + var libraryOptions = GetLibraryOptions(item, imageType, imageCount); + + // Path must exist if set: is read in as a stream by AsyncFile.OpenRead + var imageResponse = new DynamicImageResponse + { + HasImage = true, + Format = ImageFormat.Jpg, + Path = responseHasPath ? string.Format(CultureInfo.InvariantCulture, TestDataImagePath, 0) : null, + Protocol = protocol + }; + + var dynamicProvider = new Mock(MockBehavior.Strict); + dynamicProvider.Setup(rp => rp.Name).Returns("MockDynamicProvider"); + dynamicProvider.Setup(rp => rp.GetSupportedImages(item)) + .Returns(new[] { imageType }); + dynamicProvider.Setup(rp => rp.GetImage(item, imageType, It.IsAny())) + .ReturnsAsync(imageResponse); + + var refreshOptions = new ImageRefreshOptions(null); + + var providerManager = new Mock(MockBehavior.Strict); + providerManager.Setup(pm => pm.SaveImage(item, It.IsAny(), It.IsAny(), imageType, null, It.IsAny())) + .Callback((callbackItem, _, _, callbackType, _, _) => callbackItem.SetImagePath(callbackType, 0, new FileSystemMetadata())) + .Returns(Task.CompletedTask); + var itemImageProvider = GetItemImageProvider(providerManager.Object, null); + var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { dynamicProvider.Object }, refreshOptions, CancellationToken.None); + Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); // dynamic provider unable to return multiple images Assert.Single(item.GetImages(imageType)); - Assert.Equal(expectedPath, item.GetImagePath(imageType, 0)); + if (protocol == MediaProtocol.Http) + { + Assert.Equal(imageResponse.Path, item.GetImagePath(imageType, 0)); + } } [Theory] - [MemberData(nameof(GetImageTypesWithCount))] - public async void RefreshImages_PopulatedItemPopulatedProviderRemote_NoChange(ImageType imageType, int imageCount) + [InlineData(ImageType.Primary, 1, false)] + [InlineData(ImageType.Backdrop, 1, false)] + [InlineData(ImageType.Backdrop, 2, false)] + [InlineData(ImageType.Screenshot, 2, false)] + [InlineData(ImageType.Primary, 1, true)] + [InlineData(ImageType.Backdrop, 1, true)] + [InlineData(ImageType.Backdrop, 2, true)] + [InlineData(ImageType.Screenshot, 2, true)] + public async void RefreshImages_PopulatedItemPopulatedProviderRemote_UpdatesImagesIfForced(ImageType imageType, int imageCount, bool forceRefresh) { var item = GetItemWithImages(imageType, imageCount, false); @@ -385,7 +327,12 @@ namespace Jellyfin.Providers.Tests.Manager remoteProvider.Setup(rp => rp.GetSupportedImages(item)) .Returns(new[] { imageType }); - var refreshOptions = new ImageRefreshOptions(null); + var refreshOptions = forceRefresh + ? new ImageRefreshOptions(null) + { + ImageRefreshMode = MetadataRefreshMode.FullRefresh, ReplaceAllImages = true + } + : new ImageRefreshOptions(null); var remoteInfo = new List(); for (int i = 0; i < imageCount; i++) @@ -401,11 +348,22 @@ namespace Jellyfin.Providers.Tests.Manager var providerManager = new Mock(MockBehavior.Strict); providerManager.Setup(pm => pm.GetAvailableRemoteImages(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(remoteInfo); - var itemImageProvider = GetItemImageProvider(providerManager.Object, null); + var itemImageProvider = GetItemImageProvider(providerManager.Object, new Mock()); var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { remoteProvider.Object }, refreshOptions, CancellationToken.None); - Assert.False(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); + Assert.Equal(forceRefresh, result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); Assert.Equal(imageCount, item.GetImages(imageType).Count()); + foreach (var image in item.GetImages(imageType)) + { + if (forceRefresh) + { + Assert.Matches(@"image url [0-9]", image.Path); + } + else + { + Assert.DoesNotMatch(@"image url [0-9]", image.Path); + } + } } [Theory] @@ -519,50 +477,6 @@ namespace Jellyfin.Providers.Tests.Manager } } - [Theory] - [MemberData(nameof(GetImageTypesWithCount))] - public async void RefreshImages_PopulatedItemPopulatedProviderRemoteFullRefresh_UpdatesImages(ImageType imageType, int imageCount) - { - var item = GetItemWithImages(imageType, imageCount, false); - - var libraryOptions = GetLibraryOptions(item, imageType, imageCount); - - var remoteProvider = new Mock(MockBehavior.Strict); - remoteProvider.Setup(rp => rp.Name).Returns("MockRemoteProvider"); - remoteProvider.Setup(rp => rp.GetSupportedImages(item)) - .Returns(new[] { imageType }); - - var refreshOptions = new ImageRefreshOptions(null) - { - ImageRefreshMode = MetadataRefreshMode.FullRefresh, - ReplaceAllImages = true - }; - - var remoteInfo = new List(); - for (int i = 0; i < imageCount; i++) - { - remoteInfo.Add(new RemoteImageInfo - { - Type = imageType, - Url = "image url " + i, - Width = 1 // min width is set to 0, this will always pass - }); - } - - var providerManager = new Mock(MockBehavior.Strict); - providerManager.Setup(pm => pm.GetAvailableRemoteImages(It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(remoteInfo); - var itemImageProvider = GetItemImageProvider(providerManager.Object, new Mock()); - var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { remoteProvider.Object }, refreshOptions, CancellationToken.None); - - Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate)); - Assert.Equal(imageCount, item.GetImages(imageType).Count()); - foreach (var image in item.GetImages(imageType)) - { - Assert.Matches(@"image url [0-9]", image.Path); - } - } - [Theory] [MemberData(nameof(GetImageTypesWithCount))] public async void RefreshImages_PopulatedItemEmptyProviderRemoteFullRefresh_DoesntClearImages(ImageType imageType, int imageCount) From 416894008ea641b8d069ee482f7e63d2b9d5723d Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 3 Nov 2021 14:02:57 +0100 Subject: [PATCH 261/278] Minor improvements * Removed some allocations * Removed some useless abstractions --- MediaBrowser.Controller/Entities/BaseItem.cs | 106 ++++++------------ .../Manager/ItemImageProviderTests.cs | 55 ++++----- 2 files changed, 64 insertions(+), 97 deletions(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 02ee97b23fe..0df70705e53 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -84,8 +84,6 @@ namespace MediaBrowser.Controller.Entities Model.Entities.ExtraType.Scene }; - public static readonly char[] SlugReplaceChars = { '?', '/', '&' }; - /// /// The supported extra folder names and types. See . /// @@ -354,11 +352,6 @@ namespace MediaBrowser.Controller.Entities { get { - // if (IsOffline) - // { - // return LocationType.Offline; - // } - var path = Path; if (string.IsNullOrEmpty(path)) { @@ -391,7 +384,7 @@ namespace MediaBrowser.Controller.Entities } [JsonIgnore] - public bool IsFileProtocol => IsPathProtocol(MediaProtocol.File); + public bool IsFileProtocol => PathProtocol == MediaProtocol.File; [JsonIgnore] public bool HasPathProtocol => PathProtocol.HasValue; @@ -583,14 +576,7 @@ namespace MediaBrowser.Controller.Entities } [JsonIgnore] - public virtual Guid DisplayParentId - { - get - { - var parentId = ParentId; - return parentId; - } - } + public virtual Guid DisplayParentId => ParentId; [JsonIgnore] public BaseItem DisplayParent @@ -853,13 +839,6 @@ namespace MediaBrowser.Controller.Entities return Id.ToString("N", CultureInfo.InvariantCulture); } - public bool IsPathProtocol(MediaProtocol protocol) - { - var current = PathProtocol; - - return current.HasValue && current.Value == protocol; - } - private List> GetSortChunks(string s1) { var list = new List>(); @@ -987,7 +966,7 @@ namespace MediaBrowser.Controller.Entities ReadOnlySpan idString = Id.ToString("N", CultureInfo.InvariantCulture); - return System.IO.Path.Join(basePath, "library", idString.Slice(0, 2), idString); + return System.IO.Path.Join(basePath, "library", idString[..2], idString); } /// @@ -1302,8 +1281,7 @@ namespace MediaBrowser.Controller.Entities terms.Add(item.Name); } - var video = item as Video; - if (video != null) + if (item is Video video) { if (video.Video3DFormat.HasValue) { @@ -1338,7 +1316,7 @@ namespace MediaBrowser.Controller.Entities } } - return string.Join('/', terms.ToArray()); + return string.Join('/', terms); } /// @@ -1361,9 +1339,7 @@ namespace MediaBrowser.Controller.Entities .Select(audio => { // Try to retrieve it from the db. If we don't find it, use the resolved version - var dbItem = LibraryManager.GetItemById(audio.Id) as Audio.Audio; - - if (dbItem != null) + if (LibraryManager.GetItemById(audio.Id) is Audio.Audio dbItem) { audio = dbItem; } @@ -1570,8 +1546,7 @@ namespace MediaBrowser.Controller.Entities } } - var hasTrailers = this as IHasTrailers; - if (hasTrailers != null) + if (this is IHasTrailers hasTrailers) { localTrailersChanged = await RefreshLocalTrailers(hasTrailers, options, fileSystemChildren, cancellationToken).ConfigureAwait(false); } @@ -2268,7 +2243,11 @@ namespace MediaBrowser.Controller.Entities var existingImage = GetImageInfo(image.Type, index); - if (existingImage != null) + if (existingImage == null) + { + AddImage(image); + } + else { existingImage.Path = image.Path; existingImage.DateModified = image.DateModified; @@ -2276,15 +2255,6 @@ namespace MediaBrowser.Controller.Entities existingImage.Height = image.Height; existingImage.BlurHash = image.BlurHash; } - else - { - var current = ImageInfos; - var currentCount = current.Length; - var newArr = new ItemImageInfo[currentCount + 1]; - current.CopyTo(newArr, 0); - newArr[currentCount] = image; - ImageInfos = newArr; - } } public void SetImagePath(ImageType type, int index, FileSystemMetadata file) @@ -2298,7 +2268,7 @@ namespace MediaBrowser.Controller.Entities if (image == null) { - ImageInfos = ImageInfos.Concat(new[] { GetImageInfo(file, type) }).ToArray(); + AddImage(GetImageInfo(file, type)); } else { @@ -2342,7 +2312,7 @@ namespace MediaBrowser.Controller.Entities public void RemoveImage(ItemImageInfo image) { - RemoveImages(new List { image }); + RemoveImages(new[] { image }); } public void RemoveImages(IEnumerable deletedImages) @@ -2350,6 +2320,16 @@ namespace MediaBrowser.Controller.Entities ImageInfos = ImageInfos.Except(deletedImages).ToArray(); } + public void AddImage(ItemImageInfo image) + { + var current = ImageInfos; + var currentCount = current.Length; + var newArr = new ItemImageInfo[currentCount + 1]; + current.CopyTo(newArr, 0); + newArr[currentCount] = image; + ImageInfos = newArr; + } + public virtual Task UpdateToRepositoryAsync(ItemUpdateType updateReason, CancellationToken cancellationToken) => LibraryManager.UpdateItemAsync(this, GetParent(), updateReason, cancellationToken); @@ -2373,7 +2353,7 @@ namespace MediaBrowser.Controller.Entities if (deletedImages.Count > 0) { - ImageInfos = ImageInfos.Except(deletedImages).ToArray(); + RemoveImages(deletedImages); } return deletedImages.Count > 0; @@ -2715,7 +2695,7 @@ namespace MediaBrowser.Controller.Entities protected static string GetMappedPath(BaseItem item, string path, MediaProtocol? protocol) { - if (protocol.HasValue && protocol.Value == MediaProtocol.File) + if (protocol == MediaProtocol.File) { return LibraryManager.GetPathAfterNetworkSubstitution(path, item); } @@ -2743,8 +2723,10 @@ namespace MediaBrowser.Controller.Entities protected Task RefreshMetadataForOwnedItem(BaseItem ownedItem, bool copyTitleMetadata, MetadataRefreshOptions options, CancellationToken cancellationToken) { - var newOptions = new MetadataRefreshOptions(options); - newOptions.SearchResult = null; + var newOptions = new MetadataRefreshOptions(options) + { + SearchResult = null + }; var item = this; @@ -2805,8 +2787,10 @@ namespace MediaBrowser.Controller.Entities protected Task RefreshMetadataForOwnedVideo(MetadataRefreshOptions options, bool copyTitleMetadata, string path, CancellationToken cancellationToken) { - var newOptions = new MetadataRefreshOptions(options); - newOptions.SearchResult = null; + var newOptions = new MetadataRefreshOptions(options) + { + SearchResult = null + }; var id = LibraryManager.GetNewItemId(path, typeof(Video)); @@ -2820,14 +2804,6 @@ namespace MediaBrowser.Controller.Entities newOptions.ForceSave = true; } - // var parentId = Id; - // if (!video.IsOwnedItem || video.ParentId != parentId) - // { - // video.IsOwnedItem = true; - // video.ParentId = parentId; - // newOptions.ForceSave = true; - // } - if (video == null) { return Task.FromResult(true); @@ -2911,7 +2887,7 @@ namespace MediaBrowser.Controller.Entities .Select(i => i.OfficialRating) .Where(i => !string.IsNullOrEmpty(i)) .Distinct(StringComparer.OrdinalIgnoreCase) - .Select(i => new Tuple(i, LocalizationManager.GetRatingLevel(i))) + .Select(i => (i, LocalizationManager.GetRatingLevel(i))) .OrderBy(i => i.Item2 ?? 1000) .Select(i => i.Item1); @@ -2958,18 +2934,6 @@ namespace MediaBrowser.Controller.Entities .Where(i => i.ExtraType.HasValue && extraTypes.Contains(i.ExtraType.Value)); } - public IEnumerable GetTrailers() - { - if (this is IHasTrailers) - { - return ((IHasTrailers)this).LocalTrailerIds.Select(LibraryManager.GetItemById).Where(i => i != null).OrderBy(i => i.SortName); - } - else - { - return Array.Empty(); - } - } - public virtual long GetRunTimeTicksForPlayState() { return RunTimeTicks ?? 0; diff --git a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs index 6d65ba2d7a8..f9ac8f46b9f 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs @@ -28,13 +28,13 @@ namespace Jellyfin.Providers.Tests.Manager { public class ItemImageProviderTests { - private static readonly string TestDataImagePath = "Test Data/Images/blank{0}.jpg"; + private const string TestDataImagePath = "Test Data/Images/blank{0}.jpg"; [Fact] public void ValidateImages_PhotoEmptyProviders_NoChange() { var itemImageProvider = GetItemImageProvider(null, null); - var changed = itemImageProvider.ValidateImages(new Photo(), new List(), null); + var changed = itemImageProvider.ValidateImages(new Photo(), Enumerable.Empty(), null); Assert.False(changed); } @@ -43,7 +43,7 @@ namespace Jellyfin.Providers.Tests.Manager public void ValidateImages_EmptyItemEmptyProviders_NoChange() { var itemImageProvider = GetItemImageProvider(null, null); - var changed = itemImageProvider.ValidateImages(new MovieWithScreenshots(), new List(), null); + var changed = itemImageProvider.ValidateImages(new MovieWithScreenshots(), Enumerable.Empty(), null); Assert.False(changed); } @@ -73,7 +73,7 @@ namespace Jellyfin.Providers.Tests.Manager var imageProvider = GetImageProvider(imageType, imageCount, true); var itemImageProvider = GetItemImageProvider(null, null); - var changed = itemImageProvider.ValidateImages(item, new List { imageProvider }, null); + var changed = itemImageProvider.ValidateImages(item, new[] { imageProvider }, null); Assert.True(changed); Assert.Equal(imageCount, item.GetImages(imageType).Count()); @@ -86,7 +86,7 @@ namespace Jellyfin.Providers.Tests.Manager var item = GetItemWithImages(imageType, imageCount, true); var itemImageProvider = GetItemImageProvider(null, null); - var changed = itemImageProvider.ValidateImages(item, new List(), null); + var changed = itemImageProvider.ValidateImages(item, Enumerable.Empty(), null); Assert.False(changed); Assert.Equal(imageCount, item.GetImages(imageType).Count()); @@ -99,7 +99,7 @@ namespace Jellyfin.Providers.Tests.Manager var item = GetItemWithImages(imageType, imageCount, false); var itemImageProvider = GetItemImageProvider(null, null); - var changed = itemImageProvider.ValidateImages(item, new List(), null); + var changed = itemImageProvider.ValidateImages(item, Enumerable.Empty(), null); Assert.True(changed); Assert.Empty(item.GetImages(imageType)); @@ -109,7 +109,7 @@ namespace Jellyfin.Providers.Tests.Manager public void MergeImages_EmptyItemNewImagesEmpty_NoChange() { var itemImageProvider = GetItemImageProvider(null, null); - var changed = itemImageProvider.MergeImages(new MovieWithScreenshots(), new List()); + var changed = itemImageProvider.MergeImages(new MovieWithScreenshots(), Array.Empty()); Assert.False(changed); } @@ -237,7 +237,8 @@ namespace Jellyfin.Providers.Tests.Manager var refreshOptions = forceRefresh ? new ImageRefreshOptions(null) { - ImageRefreshMode = MetadataRefreshMode.FullRefresh, ReplaceAllImages = true + ImageRefreshMode = MetadataRefreshMode.FullRefresh, + ReplaceAllImages = true } : new ImageRefreshOptions(null); @@ -330,19 +331,20 @@ namespace Jellyfin.Providers.Tests.Manager var refreshOptions = forceRefresh ? new ImageRefreshOptions(null) { - ImageRefreshMode = MetadataRefreshMode.FullRefresh, ReplaceAllImages = true + ImageRefreshMode = MetadataRefreshMode.FullRefresh, + ReplaceAllImages = true } : new ImageRefreshOptions(null); - var remoteInfo = new List(); + var remoteInfo = new RemoteImageInfo[imageCount]; for (int i = 0; i < imageCount; i++) { - remoteInfo.Add(new RemoteImageInfo + remoteInfo[i] = new RemoteImageInfo { Type = imageType, Url = "image url " + i, Width = 1 // min width is set to 0, this will always pass - }); + }; } var providerManager = new Mock(MockBehavior.Strict); @@ -383,7 +385,7 @@ namespace Jellyfin.Providers.Tests.Manager // seek 2 so it won't short-circuit out of downloading when populated var libraryOptions = GetLibraryOptions(item, imageType, 2); - var content = "Content"; + const string Content = "Content"; var remoteProvider = new Mock(MockBehavior.Strict); remoteProvider.Setup(rp => rp.Name).Returns("MockRemoteProvider"); remoteProvider.Setup(rp => rp.GetSupportedImages(item)) @@ -393,7 +395,7 @@ namespace Jellyfin.Providers.Tests.Manager { ReasonPhrase = url, StatusCode = HttpStatusCode.OK, - Content = new StringContent(content, Encoding.UTF8, "image/jpeg") + Content = new StringContent(Content, Encoding.UTF8, "image/jpeg") }); var refreshOptions = fullRefresh @@ -404,15 +406,15 @@ namespace Jellyfin.Providers.Tests.Manager } : new ImageRefreshOptions(null); - var remoteInfo = new List(); + var remoteInfo = new RemoteImageInfo[targetImageCount]; for (int i = 0; i < targetImageCount; i++) { - remoteInfo.Add(new RemoteImageInfo + remoteInfo[i] = new RemoteImageInfo() { Type = imageType, Url = "image url " + i, Width = 1 // min width is set to 0, this will always pass - }); + }; } var providerManager = new Mock(MockBehavior.Strict); @@ -425,7 +427,7 @@ namespace Jellyfin.Providers.Tests.Manager var fileSystem = new Mock(); // match reported file size to image content length - condition for skipping already downloaded multi-images fileSystem.Setup(fs => fs.GetFileInfo(It.IsAny())) - .Returns(new FileSystemMetadata { Length = content.Length }); + .Returns(new FileSystemMetadata { Length = Content.Length }); var itemImageProvider = GetItemImageProvider(providerManager.Object, fileSystem); var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List { remoteProvider.Object }, refreshOptions, CancellationToken.None); @@ -449,15 +451,16 @@ namespace Jellyfin.Providers.Tests.Manager var refreshOptions = new ImageRefreshOptions(null); // populate remote with double the required images to verify count is trimmed to the library option count - var remoteInfo = new List(); - for (int i = 0; i < imageCount * 2; i++) + var remoteInfoCount = imageCount * 2; + var remoteInfo = new RemoteImageInfo[remoteInfoCount]; + for (int i = 0; i < remoteInfoCount; i++) { - remoteInfo.Add(new RemoteImageInfo + remoteInfo[i] = new RemoteImageInfo() { Type = imageType, Url = "image url " + i, Width = 1 // min width is set to 0, this will always pass - }); + }; } var providerManager = new Mock(MockBehavior.Strict); @@ -552,20 +555,20 @@ namespace Jellyfin.Providers.Tests.Manager /// /// Creates a list of references of the specified type and size, optionally pointing to files that exist. /// - private static List GetImages(ImageType type, int count, bool validPaths) + private static LocalImageInfo[] GetImages(ImageType type, int count, bool validPaths) { var path = validPaths ? TestDataImagePath : "invalid path {0}"; - var images = new List(count); + var images = new LocalImageInfo[count]; for (int i = 0; i < count; i++) { - images.Add(new LocalImageInfo + images[i] = new LocalImageInfo { Type = type, FileInfo = new FileSystemMetadata { FullName = string.Format(CultureInfo.InvariantCulture, path, i) } - }); + }; } return images; From 4fc0521d69fc0a1ec6b01588044e1ba5f6022e41 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Wed, 3 Nov 2021 17:16:40 +0100 Subject: [PATCH 262/278] Move ConvertToRemoteImage to TmdbClientManager --- .../Tmdb/BoxSets/TmdbBoxSetImageProvider.cs | 5 +- .../Tmdb/Movies/TmdbMovieImageProvider.cs | 4 +- .../Tmdb/People/TmdbPersonImageProvider.cs | 2 +- .../Tmdb/TV/TmdbEpisodeImageProvider.cs | 2 +- .../Tmdb/TV/TmdbSeasonImageProvider.cs | 2 +- .../Tmdb/TV/TmdbSeriesImageProvider.cs | 4 +- .../Plugins/Tmdb/TmdbClientManager.cs | 85 +++++++++++++++---- .../Plugins/Tmdb/TmdbUtils.cs | 78 ----------------- 8 files changed, 79 insertions(+), 103 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs index 17f3e635ff6..29a557c315d 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs @@ -13,7 +13,6 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; @@ -71,8 +70,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets var backdrops = collection.Images.Backdrops; var remoteImages = new List(posters.Count + backdrops.Count); - TmdbUtils.ConvertPostersToRemoteImageInfo(posters, _tmdbClientManager, language, remoteImages); - TmdbUtils.ConvertBackdropsToRemoteImageInfo(backdrops, _tmdbClientManager, language, remoteImages); + _tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language, remoteImages); + _tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs index 4336efee45a..f71f7bd10d1 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs @@ -87,8 +87,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies var backdrops = movie.Images.Backdrops; var remoteImages = new List(posters.Count + backdrops.Count); - TmdbUtils.ConvertPostersToRemoteImageInfo(posters, _tmdbClientManager, language, remoteImages); - TmdbUtils.ConvertBackdropsToRemoteImageInfo(backdrops, _tmdbClientManager, language, remoteImages); + _tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language, remoteImages); + _tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs index cad62eca3cb..7ce4cfe6768 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs @@ -63,7 +63,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People var profiles = personResult.Images.Profiles; var remoteImages = new List(profiles.Count); - TmdbUtils.ConvertProfilesToRemoteImageInfo(profiles, _tmdbClientManager, language, remoteImages); + _tmdbClientManager.ConvertProfilesToRemoteImageInfo(profiles, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs index b7dda9b5026..5eec776b5bf 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs @@ -76,7 +76,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var remoteImages = new List(stills.Count); - TmdbUtils.ConvertStillsToRemoteImageInfo(stills, _tmdbClientManager, language, remoteImages); + _tmdbClientManager.ConvertStillsToRemoteImageInfo(stills, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs index 90b324a4fe4..4446fa96654 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs @@ -63,7 +63,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var remoteImages = new List(posters.Count); - TmdbUtils.ConvertPostersToRemoteImageInfo(posters, _tmdbClientManager, language, remoteImages); + _tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs index 9f2821c404e..5ef3736c4f0 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs @@ -71,8 +71,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var backdrops = series.Images.Backdrops; var remoteImages = new List(posters.Count + backdrops.Count); - TmdbUtils.ConvertPostersToRemoteImageInfo(posters, _tmdbClientManager, language, remoteImages); - TmdbUtils.ConvertBackdropsToRemoteImageInfo(backdrops, _tmdbClientManager, language, remoteImages); + _tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language, remoteImages); + _tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language, remoteImages); return remoteImages; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index 3c7e3326993..9292530266b 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -5,6 +5,9 @@ using System.Collections.Generic; using System.Globalization; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; using Microsoft.Extensions.Caching.Memory; using TMDbLib.Client; using TMDbLib.Objects.Collections; @@ -496,16 +499,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb return GetUrl(_tmDbClient.Config.Images.PosterSizes[^1], posterPath); } - /// - /// Gets the absolute URL of the backdrop image. - /// - /// The relative URL of the backdrop image. - /// The absolute URL. - public string GetBackdropUrl(string backdropPath) - { - return GetUrl(_tmDbClient.Config.Images.BackdropSizes[^1], backdropPath); - } - /// /// Gets the absolute URL of the profile image. /// @@ -517,13 +510,75 @@ namespace MediaBrowser.Providers.Plugins.Tmdb } /// - /// Gets the absolute URL of the still image. + /// Converts poster s into s. /// - /// The relative URL of the still image. - /// The absolute URL. - public string GetStillUrl(string filePath) + /// The input images. + /// The requested language. + /// The collection to add the remote images into. + public void ConvertPostersToRemoteImageInfo(List images, string requestLanguage, List results) { - return GetUrl(_tmDbClient.Config.Images.StillSizes[^1], filePath); + ConvertToRemoteImageInfo(images, _tmDbClient.Config.Images.PosterSizes[^1], ImageType.Primary, requestLanguage, results); + } + + /// + /// Converts backdrop s into s. + /// + /// The input images. + /// The requested language. + /// The collection to add the remote images into. + public void ConvertBackdropsToRemoteImageInfo(List images, string requestLanguage, List results) + { + ConvertToRemoteImageInfo(images, _tmDbClient.Config.Images.BackdropSizes[^1], ImageType.Backdrop, requestLanguage, results); + } + + /// + /// Converts profile s into s. + /// + /// The input images. + /// The requested language. + /// The collection to add the remote images into. + public void ConvertProfilesToRemoteImageInfo(List images, string requestLanguage, List results) + { + ConvertToRemoteImageInfo(images, _tmDbClient.Config.Images.ProfileSizes[^1], ImageType.Primary, requestLanguage, results); + } + + /// + /// Converts still s into s. + /// + /// The input images. + /// The requested language. + /// The collection to add the remote images into. + public void ConvertStillsToRemoteImageInfo(List images, string requestLanguage, List results) + { + ConvertToRemoteImageInfo(images, _tmDbClient.Config.Images.StillSizes[^1], ImageType.Primary, requestLanguage, results); + } + + /// + /// Converts s into s. + /// + /// The input images. + /// The size of the image to fetch. + /// The type of the image. + /// The requested language. + /// The collection to add the remote images into. + private void ConvertToRemoteImageInfo(List images, string size, ImageType type, string requestLanguage, List results) + { + for (var i = 0; i < images.Count; i++) + { + var image = images[i]; + results.Add(new RemoteImageInfo + { + Url = GetUrl(size, image.FilePath), + CommunityRating = image.VoteAverage, + VoteCount = image.VoteCount, + Width = image.Width, + Height = image.Height, + Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, requestLanguage), + ProviderName = TmdbUtils.ProviderName, + Type = type, + RatingType = RatingType.Score + }); + } } private Task EnsureClientConfigAsync() diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index 3cdab601ac4..58ab9f54737 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; using TMDbLib.Objects.General; namespace MediaBrowser.Providers.Plugins.Tmdb @@ -194,81 +192,5 @@ namespace MediaBrowser.Providers.Plugins.Tmdb return newRating.Replace("DE-", "FSK-", StringComparison.OrdinalIgnoreCase); } - - /// - /// Converts poster s into s. - /// - /// The input images. - /// The client manager to use for resolving image urls. - /// The requested language. - /// The collection to add the remote images into. - public static void ConvertPostersToRemoteImageInfo(List images, TmdbClientManager tmdbClientManager, string requestLanguage, List results) - { - ConvertToRemoteImageInfo(images, tmdbClientManager.GetPosterUrl, ImageType.Primary, requestLanguage, results); - } - - /// - /// Converts backdrop s into s. - /// - /// The input images. - /// The client manager to use for resolving image urls. - /// The requested language. - /// The collection to add the remote images into. - public static void ConvertBackdropsToRemoteImageInfo(List images, TmdbClientManager tmdbClientManager, string requestLanguage, List results) - { - ConvertToRemoteImageInfo(images, tmdbClientManager.GetBackdropUrl, ImageType.Backdrop, requestLanguage, results); - } - - /// - /// Converts profile s into s. - /// - /// The input images. - /// The client manager to use for resolving image urls. - /// The requested language. - /// The collection to add the remote images into. - public static void ConvertProfilesToRemoteImageInfo(List images, TmdbClientManager tmdbClientManager, string requestLanguage, List results) - { - ConvertToRemoteImageInfo(images, tmdbClientManager.GetProfileUrl, ImageType.Primary, requestLanguage, results); - } - - /// - /// Converts still s into s. - /// - /// The input images. - /// The client manager to use for resolving image urls. - /// The requested language. - /// The collection to add the remote images into. - public static void ConvertStillsToRemoteImageInfo(List images, TmdbClientManager tmdbClientManager, string requestLanguage, List results) - { - ConvertToRemoteImageInfo(images, tmdbClientManager.GetStillUrl, ImageType.Primary, requestLanguage, results); - } - - /// - /// Converts s into s. - /// - /// The input images. - /// The relevant GetTypeUrl function to get the absolute url of the image. - /// The type of the image. - /// The requested language. - /// The collection to add the remote images into. - private static void ConvertToRemoteImageInfo(List images, Func imageUrlConverter, ImageType type, string requestLanguage, List results) - { - for (var i = 0; i < images.Count; i++) - { - var image = images[i]; - results.Add(new RemoteImageInfo - { - Url = imageUrlConverter(image.FilePath), - CommunityRating = image.VoteAverage, - VoteCount = image.VoteCount, - Width = image.Width, - Height = image.Height, - Language = AdjustImageLanguage(image.Iso_639_1, requestLanguage), - ProviderName = ProviderName, - Type = type, - RatingType = RatingType.Score - }); - } - } } } From 924c6682b9e222c138796dcdc6fd7170ef8c268d Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 4 Nov 2021 01:06:21 +0100 Subject: [PATCH 263/278] Remove unused IHasScreenshots interface --- .../Entities/IHasScreenshots.cs | 9 ------- .../Images/LocalImageProvider.cs | 10 -------- .../Manager/ItemImageProvider.cs | 11 --------- .../Manager/ItemImageProviderTests.cs | 24 ++++++------------- 4 files changed, 7 insertions(+), 47 deletions(-) delete mode 100644 MediaBrowser.Controller/Entities/IHasScreenshots.cs diff --git a/MediaBrowser.Controller/Entities/IHasScreenshots.cs b/MediaBrowser.Controller/Entities/IHasScreenshots.cs deleted file mode 100644 index ae01c223ede..00000000000 --- a/MediaBrowser.Controller/Entities/IHasScreenshots.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MediaBrowser.Controller.Entities -{ - /// - /// The item has screenshots. - /// - public interface IHasScreenshots - { - } -} diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index 988581df92b..f7914780387 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -256,11 +256,6 @@ namespace MediaBrowser.LocalMetadata.Images { PopulateBackdrops(item, images, files, imagePrefix, isInMixedFolder); } - - if (item is IHasScreenshots) - { - PopulateScreenshots(images, files, imagePrefix, isInMixedFolder); - } } private void PopulatePrimaryImages(BaseItem item, List images, List files, string imagePrefix, bool isInMixedFolder) @@ -363,11 +358,6 @@ namespace MediaBrowser.LocalMetadata.Images })); } - private void PopulateScreenshots(List images, List files, string imagePrefix, bool isInMixedFolder) - { - PopulateBackdrops(images, files, imagePrefix, "screenshot", "screenshot", isInMixedFolder, ImageType.Screenshot); - } - private void PopulateBackdrops(List images, List files, string imagePrefix, string firstFileName, string subsequentFileNamePrefix, bool isInMixedFolder, ImageType type) { AddImage(files, images, imagePrefix + firstFileName, type); diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 8d5795f8e18..d5959db7727 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -343,12 +343,6 @@ namespace MediaBrowser.Providers.Manager minWidth = savedOptions.GetMinWidth(ImageType.Backdrop); await DownloadMultiImages(item, ImageType.Backdrop, refreshOptions, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); - - if (item is IHasScreenshots) - { - minWidth = savedOptions.GetMinWidth(ImageType.Screenshot); - await DownloadMultiImages(item, ImageType.Screenshot, refreshOptions, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false); - } } catch (OperationCanceledException) { @@ -438,11 +432,6 @@ namespace MediaBrowser.Providers.Manager changed = true; } - if (item is IHasScreenshots && UpdateMultiImages(item, images, ImageType.Screenshot)) - { - changed = true; - } - return changed; } diff --git a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs index f9ac8f46b9f..6011c8dd528 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs @@ -10,7 +10,6 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; @@ -43,7 +42,7 @@ namespace Jellyfin.Providers.Tests.Manager public void ValidateImages_EmptyItemEmptyProviders_NoChange() { var itemImageProvider = GetItemImageProvider(null, null); - var changed = itemImageProvider.ValidateImages(new MovieWithScreenshots(), Enumerable.Empty(), null); + var changed = itemImageProvider.ValidateImages(new Video(), Enumerable.Empty(), null); Assert.False(changed); } @@ -55,8 +54,7 @@ namespace Jellyfin.Providers.Tests.Manager // minimal test cases that hit different handling { ImageType.Primary, 1 }, { ImageType.Backdrop, 1 }, - { ImageType.Backdrop, 2 }, - { ImageType.Screenshot, 1 } + { ImageType.Backdrop, 2 } }; return theoryTypes; @@ -69,7 +67,7 @@ namespace Jellyfin.Providers.Tests.Manager // Has to exist for querying DateModified time on file, results stored but not checked so not populating BaseItem.FileSystem = Mock.Of(); - var item = new MovieWithScreenshots(); + var item = new Video(); var imageProvider = GetImageProvider(imageType, imageCount, true); var itemImageProvider = GetItemImageProvider(null, null); @@ -109,7 +107,7 @@ namespace Jellyfin.Providers.Tests.Manager public void MergeImages_EmptyItemNewImagesEmpty_NoChange() { var itemImageProvider = GetItemImageProvider(null, null); - var changed = itemImageProvider.MergeImages(new MovieWithScreenshots(), Array.Empty()); + var changed = itemImageProvider.MergeImages(new Video(), Array.Empty()); Assert.False(changed); } @@ -270,7 +268,7 @@ namespace Jellyfin.Providers.Tests.Manager // Has to exist for querying DateModified time on file, results stored but not checked so not populating BaseItem.FileSystem = Mock.Of(); - var item = new MovieWithScreenshots(); + var item = new Video(); var libraryOptions = GetLibraryOptions(item, imageType, imageCount); @@ -312,11 +310,9 @@ namespace Jellyfin.Providers.Tests.Manager [InlineData(ImageType.Primary, 1, false)] [InlineData(ImageType.Backdrop, 1, false)] [InlineData(ImageType.Backdrop, 2, false)] - [InlineData(ImageType.Screenshot, 2, false)] [InlineData(ImageType.Primary, 1, true)] [InlineData(ImageType.Backdrop, 1, true)] [InlineData(ImageType.Backdrop, 2, true)] - [InlineData(ImageType.Screenshot, 2, true)] public async void RefreshImages_PopulatedItemPopulatedProviderRemote_UpdatesImagesIfForced(ImageType imageType, int imageCount, bool forceRefresh) { var item = GetItemWithImages(imageType, imageCount, false); @@ -439,7 +435,7 @@ namespace Jellyfin.Providers.Tests.Manager [MemberData(nameof(GetImageTypesWithCount))] public async void RefreshImages_EmptyItemPopulatedProviderRemoteExtras_LimitsImages(ImageType imageType, int imageCount) { - var item = new MovieWithScreenshots(); + var item = new Video(); var libraryOptions = GetLibraryOptions(item, imageType, imageCount); @@ -528,7 +524,7 @@ namespace Jellyfin.Providers.Tests.Manager // Has to exist for querying DateModified time on file, results stored but not checked so not populating BaseItem.FileSystem ??= Mock.Of(); - var item = new MovieWithScreenshots(); + var item = new Video(); var path = validPaths ? TestDataImagePath : "invalid path {0}"; for (int i = 0; i < count; i++) @@ -599,11 +595,5 @@ namespace Jellyfin.Providers.Tests.Manager } }; } - - // Create a class that implements IHasScreenshots for testing since no BaseItem class is also IHasScreenshots - private class MovieWithScreenshots : Movie, IHasScreenshots - { - // No contents - } } } From 70b9f9bf560e4f9ff47ecb00c22561e3065a20de Mon Sep 17 00:00:00 2001 From: nextlooper42 Date: Tue, 2 Nov 2021 22:26:20 +0000 Subject: [PATCH 264/278] Translated using Weblate (Slovak) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sk/ --- Emby.Server.Implementations/Localization/Core/sk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json index ad90bd8134a..37da7d5ab38 100644 --- a/Emby.Server.Implementations/Localization/Core/sk.json +++ b/Emby.Server.Implementations/Localization/Core/sk.json @@ -39,7 +39,7 @@ "MixedContent": "Zmiešaný obsah", "Movies": "Filmy", "Music": "Hudba", - "MusicVideos": "Hudobné videá", + "MusicVideos": "Hudobné videoklipy", "NameInstallFailed": "Inštalácia {0} zlyhala", "NameSeasonNumber": "Séria {0}", "NameSeasonUnknown": "Neznáma séria", From 654bd6fff141fb53661e2a99fc6a48478c92e8dd Mon Sep 17 00:00:00 2001 From: WWWesten Date: Thu, 4 Nov 2021 08:18:21 +0000 Subject: [PATCH 265/278] Translated using Weblate (Turkish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/tr/ --- Emby.Server.Implementations/Localization/Core/tr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index e661299c417..8fadb88ac84 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -8,7 +8,7 @@ "CameraImageUploadedFrom": "{0} 'den yeni bir kamera resmi yüklendi", "Channels": "Kanallar", "ChapterNameValue": "Bölüm {0}", - "Collections": "Koleksiyon", + "Collections": "Koleksiyonlar", "DeviceOfflineWithName": "{0} bağlantısı kesildi", "DeviceOnlineWithName": "{0} bağlı", "FailedLoginAttemptWithUserName": "{0} adresinden giriş başarısız oldu", From 5aadf8c291df8a9f9a3bb6d4407979fc456ab6d4 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Wed, 3 Nov 2021 06:33:55 +0000 Subject: [PATCH 266/278] Translated using Weblate (Esperanto) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eo/ --- .../Localization/Core/eo.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json index f92b5f67303..8ac466908c6 100644 --- a/Emby.Server.Implementations/Localization/Core/eo.json +++ b/Emby.Server.Implementations/Localization/Core/eo.json @@ -1,5 +1,5 @@ { - "NotificationOptionInstallationFailed": "Instalada fiasko", + "NotificationOptionInstallationFailed": "Instalada malsukceso", "NotificationOptionAudioPlaybackStopped": "Ludado de sono haltis", "NotificationOptionAudioPlayback": "Ludado de sono lanĉis", "NameSeasonUnknown": "Sezono Nekonata", @@ -48,17 +48,17 @@ "Shows": "Serioj", "HeaderFavoriteShows": "Favorataj Serioj", "TvShows": "TV-serioj", - "Favorites": "Favoratoj", + "Favorites": "Favorataj", "TaskCleanLogs": "Purigi Ĵurnalan Katalogon", - "TaskRefreshLibrary": "Skanu Plurmeditekon", + "TaskRefreshLibrary": "Skani Plurmeditekon", "ValueSpecialEpisodeName": "Speciala - {0}", - "TaskOptimizeDatabase": "Optimigi datumbazon", + "TaskOptimizeDatabase": "Optimigi datenbazon", "TaskRefreshChannels": "Refreŝigi Kanalojn", "TaskUpdatePlugins": "Ĝisdatigi Kromprogramojn", "TaskRefreshPeople": "Refreŝigi Homojn", "TasksChannelsCategory": "Interretaj Kanaloj", "ProviderValue": "Provizanto: {0}", - "NotificationOptionPluginError": "Kromprograma malsukceso", + "NotificationOptionPluginError": "Kromprogramo malsukcesis", "MixedContent": "Miksita enhavo", "TasksApplicationCategory": "Aplikaĵo", "TasksMaintenanceCategory": "Prizorgado", @@ -102,9 +102,9 @@ "MessageApplicationUpdatedTo": "Jellyfin Server estis ĝisdatigita al {0}", "MessageApplicationUpdated": "Jellyfin Server estis ĝisdatigita", "TaskRefreshChannelsDescription": "Refreŝigas informon pri interretaj kanaloj.", - "TaskDownloadMissingSubtitles": "Elŝutu mankantajn subtekstojn", + "TaskDownloadMissingSubtitles": "Elŝuti mankantajn subtekstojn", "TaskCleanTranscode": "Malplenigi Transkodadan Katalogon", - "TaskRefreshChapterImages": "Eltiru Ĉapitro-Bildojn", + "TaskRefreshChapterImages": "Eltiri Ĉapitraj Bildojn", "TaskCleanCache": "Malplenigi Staplan Katalogon", "TaskCleanActivityLog": "Malplenigi Aktivecan Ĵurnalon", "PluginUpdatedWithName": "{0} estis ĝisdatigita", From c0bab5c173fbc13fc4faccbf3d231d5317b284ee Mon Sep 17 00:00:00 2001 From: cvium Date: Thu, 4 Nov 2021 11:50:46 +0100 Subject: [PATCH 267/278] Make sure ReadToDescendant was successful, #6773 --- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 9d558b6ce73..5ce22da6a8c 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -785,7 +785,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers case "fanart": { var subtree = reader.ReadSubtree(); - subtree.ReadToDescendant("thumb"); + if (!subtree.ReadToDescendant("thumb")) + { + break; + } + FetchThumbNode(subtree, itemResult); break; } From f91839dd8ce9c5723b3cab42a5c5a4410f7bf7e8 Mon Sep 17 00:00:00 2001 From: Thibault Nocchi <1619359+ThibaultNocchi@users.noreply.github.com> Date: Thu, 4 Nov 2021 15:44:15 +0100 Subject: [PATCH 268/278] Fix WebVTT region to spec --- MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs index 6d56dda91fa..38ef57dee39 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs @@ -19,12 +19,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { writer.WriteLine("WEBVTT"); writer.WriteLine(); - writer.WriteLine("REGION"); - writer.WriteLine("id:subtitle"); - writer.WriteLine("width:80%"); - writer.WriteLine("lines:3"); - writer.WriteLine("regionanchor:50%,100%"); - writer.WriteLine("viewportanchor:50%,90%"); + writer.WriteLine("Region: id:subtitle width:80% lines:3 regionanchor:50%,100% viewportanchor:50%,90%"); writer.WriteLine(); foreach (var trackEvent in info.TrackEvents) { @@ -39,7 +34,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles endTime = startTime.Add(TimeSpan.FromMilliseconds(1)); } - writer.WriteLine(@"{0:hh\:mm\:ss\.fff} --> {1:hh\:mm\:ss\.fff} region:subtitle", startTime, endTime); + writer.WriteLine(@"{0:hh\:mm\:ss\.fff} --> {1:hh\:mm\:ss\.fff} region:subtitle line:90%", startTime, endTime); var text = trackEvent.Text; From 564990964d01b146378e253e17f7414ac129e732 Mon Sep 17 00:00:00 2001 From: Julien Voisin Date: Thu, 4 Nov 2021 16:15:42 +0100 Subject: [PATCH 269/278] Add a bit of hardening to the systemd service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tested in an unprivileged lxc container, so it shouldn't™ break anything. --- debian/jellyfin.service | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/debian/jellyfin.service b/debian/jellyfin.service index b79cd47c720..e215a85362b 100644 --- a/debian/jellyfin.service +++ b/debian/jellyfin.service @@ -10,5 +10,27 @@ ExecStart = /usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELL Restart = on-failure TimeoutSec = 15 +NoNewPrivileges=true +SystemCallArchitectures=native +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK +ProtectKernelModules=True +SystemCallFilter=~@clock +SystemCallFilter=~@aio +SystemCallFilter=~@chown +SystemCallFilter=~@cpu-emulation +SystemCallFilter=~@debug +SystemCallFilter=~@keyring +SystemCallFilter=~@memlock +SystemCallFilter=~@module +SystemCallFilter=~@mount +SystemCallFilter=~@obsolete +SystemCallFilter=~@privileged +SystemCallFilter=~@raw-io +SystemCallFilter=~@reboot +SystemCallFilter=~@setuid +SystemCallFilter=~@swap +SystemCallErrorNumber=EPERM + + [Install] WantedBy = multi-user.target From 6c76d3053890dfaf539994d01dca415daebe70f6 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Thu, 4 Nov 2021 23:58:32 +0100 Subject: [PATCH 270/278] Add missing checkboxes --- .../Plugins/Tmdb/Configuration/config.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html index bce647f2adf..95d6691d9c7 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html @@ -12,6 +12,14 @@ Include adult content in search results. + +
From c8eba90c178821e11a4b9cdfc7f722bc8af8aac1 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Fri, 5 Nov 2021 00:38:50 +0100 Subject: [PATCH 271/278] Add cast limit to tmdb plugin settings --- .../Tmdb/Configuration/PluginConfiguration.cs | 5 +++++ .../Plugins/Tmdb/Configuration/config.html | 13 +++++++++++++ .../Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 3 +-- .../Plugins/Tmdb/TV/TmdbEpisodeProvider.cs | 4 ++-- .../Plugins/Tmdb/TV/TmdbSeasonProvider.cs | 2 +- .../Plugins/Tmdb/TV/TmdbSeriesProvider.cs | 2 +- MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs | 5 ----- 7 files changed, 23 insertions(+), 11 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs index 9ac95f23e99..9a78a753628 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs @@ -21,5 +21,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// Gets or sets a value indicating whether tags should be imported for movies from TMDb. ///
public bool ExcludeTagsMovies { get; set; } + + /// + /// Gets or sets a value indicating the maximum number of cast members to fetch for an item. + /// + public int MaxCastMembers { get; set; } = 15; } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html index 95d6691d9c7..12b4c7ca4ef 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html @@ -20,6 +20,10 @@ Exclude tags/keywords from metadata fetched for movies. +
+ +
The maximum number of cast members to fetch for an item.
+

@@ -39,6 +43,14 @@ document.querySelector('#includeAdult').checked = config.IncludeAdult; document.querySelector('#excludeTagsSeries').checked = config.ExcludeTagsSeries; document.querySelector('#excludeTagsMovies').checked = config.ExcludeTagsMovies; + + var maxCastMembers = document.querySelector('#maxCastMembers'); + maxCastMembers.value = config.MaxCastMembers; + maxCastMembers.dispatchEvent(new Event('change', { + bubbles: true, + cancelable: false + })); + Dashboard.hideLoadingMsg(); }); }); @@ -52,6 +64,7 @@ config.IncludeAdult = document.querySelector('#includeAdult').checked; config.ExcludeTagsSeries = document.querySelector('#excludeTagsSeries').checked; config.ExcludeTagsMovies = document.querySelector('#excludeTagsMovies').checked; + config.MaxCastMembers = document.querySelector('#maxCastMembers').value; ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult); }); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index 9dd0678561f..fcaacc90d14 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -241,8 +241,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies if (movieResult.Credits?.Cast != null) { - // TODO configurable - foreach (var actor in movieResult.Credits.Cast.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers)) + foreach (var actor in movieResult.Credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) { var personInfo = new PersonInfo { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index 3f826843a69..8ac9d0cab16 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs @@ -154,7 +154,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV if (credits?.Cast != null) { - foreach (var actor in credits.Cast.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers)) + foreach (var actor in credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) { metadataResult.AddPerson(new PersonInfo { @@ -168,7 +168,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV if (credits?.GuestStars != null) { - foreach (var guest in credits.GuestStars.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers)) + foreach (var guest in credits.GuestStars.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) { metadataResult.AddPerson(new PersonInfo { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs index 4ac88968014..7afaddc245c 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs @@ -67,7 +67,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var credits = seasonResult.Credits; if (credits?.Cast != null) { - var cast = credits.Cast.OrderBy(c => c.Order).Take(TmdbUtils.MaxCastMembers).ToList(); + var cast = credits.Cast.OrderBy(c => c.Order).Take(Plugin.Instance.Configuration.MaxCastMembers).ToList(); for (var i = 0; i < cast.Count; i++) { result.AddPerson(new PersonInfo diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index feda15cf75d..77e22ffbf0e 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -331,7 +331,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV { if (seriesResult.Credits?.Cast != null) { - foreach (var actor in seriesResult.Credits.Cast.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers)) + foreach (var actor in seriesResult.Credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) { var personInfo = new PersonInfo { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index 58ab9f54737..a3a78103ea6 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -28,11 +28,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb ///
public const string ApiKey = "4219e299c89411838049ab0dab19ebd5"; - /// - /// Maximum number of cast members to pull. - /// - public const int MaxCastMembers = 15; - /// /// The crew types to keep. /// From 13668a6ecb40b4b68f1cfaebfb7bb3807e9a9cef Mon Sep 17 00:00:00 2001 From: WWWesten Date: Thu, 4 Nov 2021 10:35:48 +0000 Subject: [PATCH 272/278] Translated using Weblate (Russian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ru/ --- Emby.Server.Implementations/Localization/Core/ru.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index cd016b51b48..36f4e3e7c9d 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -119,6 +119,6 @@ "Undefined": "Не определено", "Forced": "Форсир-ые", "Default": "По умолчанию", - "TaskOptimizeDatabaseDescription": "Сжимает базу данных и обрезает свободное место. Выполнение этой задачи после сканирования библиотеки или внесения других изменений, предполагающих модификации базы данных, может повысить производительность.", + "TaskOptimizeDatabaseDescription": "Сжимает базу данных и вырезает свободные места. Выполнение этой задачи после сканирования библиотеки или внесения других изменений, предполагающих модификации базы данных, может повысить производительность.", "TaskOptimizeDatabase": "Оптимизировать базу данных" } From 1e93c6ae3052653efbadba29657f83c660b35941 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Thu, 4 Nov 2021 10:32:21 +0000 Subject: [PATCH 273/278] Translated using Weblate (Esperanto) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eo/ --- Emby.Server.Implementations/Localization/Core/eo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json index 8ac466908c6..12541a756ab 100644 --- a/Emby.Server.Implementations/Localization/Core/eo.json +++ b/Emby.Server.Implementations/Localization/Core/eo.json @@ -52,7 +52,7 @@ "TaskCleanLogs": "Purigi Ĵurnalan Katalogon", "TaskRefreshLibrary": "Skani Plurmeditekon", "ValueSpecialEpisodeName": "Speciala - {0}", - "TaskOptimizeDatabase": "Optimigi datenbazon", + "TaskOptimizeDatabase": "Optimumigi datenbazon", "TaskRefreshChannels": "Refreŝigi Kanalojn", "TaskUpdatePlugins": "Ĝisdatigi Kromprogramojn", "TaskRefreshPeople": "Refreŝigi Homojn", @@ -75,7 +75,7 @@ "ServerNameNeedsToBeRestarted": "{0} devas esti relanĉita", "NotificationOptionVideoPlayback": "La videoludado lanĉis", "NotificationOptionServerRestartRequired": "Servila relanĉigo bezonata", - "TaskOptimizeDatabaseDescription": "Kompaktigas datenbazon kaj trunkas liberan lokon. Lanĉi ĉi tiun taskon post la teka skanado aŭ fari aliajn ŝanĝojn, kiuj implicas datenbazajn modifojn, povus plibonigi rendimenton.", + "TaskOptimizeDatabaseDescription": "Kompaktigas datenbazon kaj trunkas liberan lokon. Lanĉi ĉi tiun taskon post la plurmediteka skanado aŭ fari aliajn ŝanĝojn, kiuj implicas datenbazajn modifojn, povus plibonigi rendimenton.", "TaskUpdatePluginsDescription": "Elŝutas kaj instalas ĝisdatigojn por kromprogramojn, kiuj estas agorditaj por ĝisdatigi aŭtomate.", "TaskDownloadMissingSubtitlesDescription": "Serĉas en interreto mankantajn subtekstojn surbaze de metadatena agordaro.", "TaskRefreshPeopleDescription": "Ĝisdatigas metadatenojn por aktoroj kaj reĵisoroj en via plurmediteko.", From 44dc647adb35266f592191a821970359a6fb1324 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Fri, 5 Nov 2021 19:09:44 +0100 Subject: [PATCH 274/278] Fix OpenAPI workflow not working with pull requests from forks --- .github/workflows/openapi.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml index 6e370819af9..b81875d2c80 100644 --- a/.github/workflows/openapi.yml +++ b/.github/workflows/openapi.yml @@ -3,7 +3,7 @@ on: push: branches: - master - pull_request: + pull_request_target: jobs: openapi-head: @@ -12,6 +12,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: @@ -53,7 +55,7 @@ jobs: openapi-diff: name: OpenAPI - Difference - if: ${{ github.event_name == 'pull_request' }} + if: ${{ github.event_name == 'pull_request_target' }} runs-on: ubuntu-latest needs: - openapi-head From 2491dd513c2bbbc136e33b41043c7b60aa407ca9 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Fri, 5 Nov 2021 22:12:43 +0100 Subject: [PATCH 275/278] Specify repository info in openapi head checkout --- .github/workflows/openapi.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml index b81875d2c80..798ce5898ad 100644 --- a/.github/workflows/openapi.yml +++ b/.github/workflows/openapi.yml @@ -13,7 +13,8 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 with: - ref: ${{ github.head_ref }} + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: From 07b9ba2bb4aadfea7c177df8e747b3e79409d8af Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Fri, 5 Nov 2021 22:43:09 +0100 Subject: [PATCH 276/278] Set GITHUB_TOKEN permissions to read only in OpenAPI workflow --- .github/workflows/openapi.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml index 798ce5898ad..ea9188f1b10 100644 --- a/.github/workflows/openapi.yml +++ b/.github/workflows/openapi.yml @@ -9,6 +9,7 @@ jobs: openapi-head: name: OpenAPI - HEAD runs-on: ubuntu-latest + permissions: read-all steps: - name: Checkout repository uses: actions/checkout@v2 @@ -34,6 +35,7 @@ jobs: name: OpenAPI - BASE if: ${{ github.base_ref != '' }} runs-on: ubuntu-latest + permissions: read-all steps: - name: Checkout repository uses: actions/checkout@v2 From b4bf5af7c8b169c616ca5a9bc83248a80636bedb Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Fri, 5 Nov 2021 21:31:12 +0100 Subject: [PATCH 277/278] Remove ImageType.Screenshot and ItemFields.Screenshot --- Emby.Server.Implementations/Dto/DtoService.cs | 9 ------- MediaBrowser.Controller/Entities/BaseItem.cs | 2 +- MediaBrowser.Model/Entities/ImageType.cs | 3 +++ MediaBrowser.Model/Querying/ItemFields.cs | 3 +++ MediaBrowser.Providers/Manager/ImageSaver.cs | 3 --- .../Manager/ItemImageProvider.cs | 26 +++---------------- .../Manager/ItemImageProviderTests.cs | 2 -- 7 files changed, 10 insertions(+), 38 deletions(-) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index ad76f3d6d4c..9287f527287 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -755,15 +755,6 @@ namespace Emby.Server.Implementations.Dto dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit); } - if (options.ContainsField(ItemFields.ScreenshotImageTags)) - { - var screenshotLimit = options.GetImageLimit(ImageType.Screenshot); - if (screenshotLimit > 0) - { - dto.ScreenshotImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Screenshot, screenshotLimit); - } - } - if (options.ContainsField(ItemFields.Genres)) { dto.Genres = item.Genres; diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 0df70705e53..63749b1f331 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2577,7 +2577,7 @@ namespace MediaBrowser.Controller.Entities public bool AllowsMultipleImages(ImageType type) { - return type == ImageType.Backdrop || type == ImageType.Screenshot || type == ImageType.Chapter; + return type == ImageType.Backdrop || type == ImageType.Chapter; } public Task SwapImagesAsync(ImageType type, int index1, int index2) diff --git a/MediaBrowser.Model/Entities/ImageType.cs b/MediaBrowser.Model/Entities/ImageType.cs index 6ea9ee419ed..ee741063219 100644 --- a/MediaBrowser.Model/Entities/ImageType.cs +++ b/MediaBrowser.Model/Entities/ImageType.cs @@ -1,3 +1,5 @@ +using System; + namespace MediaBrowser.Model.Entities { /// @@ -48,6 +50,7 @@ namespace MediaBrowser.Model.Entities /// /// The screenshot. /// + [Obsolete("Screenshot image type is no longer used.")] Screenshot = 8, /// diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index ef4698f3f93..e6c3a6c2605 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -1,5 +1,7 @@ #pragma warning disable CS1591 +using System; + namespace MediaBrowser.Model.Querying { /// @@ -143,6 +145,7 @@ namespace MediaBrowser.Model.Querying /// /// The screenshot image tags. /// + [Obsolete("Screenshot image type is no longer used.")] ScreenshotImageTags, SeriesPrimaryImage, diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 8ded2d14468..d2a3344bef5 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -439,9 +439,6 @@ namespace MediaBrowser.Providers.Manager case ImageType.Backdrop: filename = GetBackdropSaveFilename(item.GetImages(type), "backdrop", "backdrop", imageIndex); break; - case ImageType.Screenshot: - filename = GetBackdropSaveFilename(item.GetImages(type), "screenshot", "screenshot", imageIndex); - break; default: filename = type.ToString().ToLowerInvariant(); break; diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index d5959db7727..1022a3faed1 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -109,12 +109,6 @@ namespace MediaBrowser.Providers.Manager oldBackdropImages = item.GetImages(ImageType.Backdrop).ToArray(); } - var oldScreenshotImages = Array.Empty(); - if (refreshOptions.IsReplacingImage(ImageType.Screenshot)) - { - oldScreenshotImages = item.GetImages(ImageType.Screenshot).ToArray(); - } - var result = new RefreshResult { UpdateType = ItemUpdateType.None }; var typeName = item.GetType().Name; @@ -122,14 +116,13 @@ namespace MediaBrowser.Providers.Manager // track library limits, adding buffer to allow lazy replacing of current images var backdropLimit = typeOptions.GetLimit(ImageType.Backdrop) + oldBackdropImages.Length; - var screenshotLimit = typeOptions.GetLimit(ImageType.Screenshot) + oldScreenshotImages.Length; var downloadedImages = new List(); foreach (var provider in providers) { if (provider is IRemoteImageProvider remoteProvider) { - await RefreshFromProvider(item, remoteProvider, refreshOptions, typeOptions, backdropLimit, screenshotLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false); + await RefreshFromProvider(item, remoteProvider, refreshOptions, typeOptions, backdropLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false); continue; } @@ -145,11 +138,6 @@ namespace MediaBrowser.Providers.Manager PruneImages(item, oldBackdropImages); } - if (oldScreenshotImages.Length > 0 && oldScreenshotImages.Length < item.GetImages(ImageType.Screenshot).Count()) - { - PruneImages(item, oldScreenshotImages); - } - return result; } @@ -243,9 +231,8 @@ namespace MediaBrowser.Providers.Manager /// The images. /// The saved options. /// The backdrop limit. - /// The screenshot limit. /// true if the specified item contains images; otherwise, false. - private bool ContainsImages(BaseItem item, List images, TypeOptions savedOptions, int backdropLimit, int screenshotLimit) + private bool ContainsImages(BaseItem item, List images, TypeOptions savedOptions, int backdropLimit) { // Using .Any causes the creation of a DisplayClass aka. variable capture for (var i = 0; i < _singularImages.Length; i++) @@ -262,11 +249,6 @@ namespace MediaBrowser.Providers.Manager return false; } - if (images.Contains(ImageType.Screenshot) && item.GetImages(ImageType.Screenshot).Count() < screenshotLimit) - { - return false; - } - return true; } @@ -278,7 +260,6 @@ namespace MediaBrowser.Providers.Manager /// The refresh options. /// The saved options. /// The backdrop limit. - /// The screenshot limit. /// The downloaded images. /// The result. /// The cancellation token. @@ -289,7 +270,6 @@ namespace MediaBrowser.Providers.Manager ImageRefreshOptions refreshOptions, TypeOptions savedOptions, int backdropLimit, - int screenshotLimit, ICollection downloadedImages, RefreshResult result, CancellationToken cancellationToken) @@ -303,7 +283,7 @@ namespace MediaBrowser.Providers.Manager if (!refreshOptions.ReplaceAllImages && refreshOptions.ReplaceImages.Length == 0 && - ContainsImages(item, provider.GetSupportedImages(item).ToList(), savedOptions, backdropLimit, screenshotLimit)) + ContainsImages(item, provider.GetSupportedImages(item).ToList(), savedOptions, backdropLimit)) { return; } diff --git a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs index 6011c8dd528..9f73ed7fc4e 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs @@ -207,10 +207,8 @@ namespace Jellyfin.Providers.Tests.Manager [Theory] [InlineData(ImageType.Primary, 1, false)] [InlineData(ImageType.Backdrop, 2, false)] - [InlineData(ImageType.Screenshot, 2, false)] [InlineData(ImageType.Primary, 1, true)] [InlineData(ImageType.Backdrop, 2, true)] - [InlineData(ImageType.Screenshot, 2, true)] public async void RefreshImages_PopulatedItemPopulatedProviderDynamic_UpdatesImagesIfForced(ImageType imageType, int imageCount, bool forceRefresh) { var item = GetItemWithImages(imageType, imageCount, false); From d95c281142462277560e1f9ac8d9e28db9c7b242 Mon Sep 17 00:00:00 2001 From: cvium Date: Sat, 6 Nov 2021 22:44:05 +0100 Subject: [PATCH 278/278] Load all types when checking plugin DLLs --- Emby.Server.Implementations/Plugins/PluginManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index d52c0b2a140..d70a15dbc16 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -126,7 +126,8 @@ namespace Emby.Server.Implementations.Plugins { assembly = Assembly.LoadFrom(file); - assembly.GetExportedTypes(); + // Load all required types to verify that the plugin will load + assembly.GetTypes(); } catch (FileLoadException ex) { @@ -134,7 +135,7 @@ namespace Emby.Server.Implementations.Plugins ChangePluginState(plugin, PluginStatus.Malfunctioned); continue; } - catch (TypeLoadException ex) // Undocumented exception + catch (SystemException ex) when (ex is TypeLoadException or ReflectionTypeLoadException) // Undocumented exception { _logger.LogError(ex, "Failed to load assembly {Path}. This error occurs when a plugin references an incompatible version of one of the shared libraries. Disabling plugin.", file); ChangePluginState(plugin, PluginStatus.NotSupported);