From be89d53a9e16e16f434c35e22814f1d9fe3fc16d Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 3 Feb 2019 20:15:23 -0500 Subject: [PATCH 01/52] Handle new option parser properly --- .../debian-package-x64/pkg-src/conf/jellyfin | 16 ++++++++++------ .../pkg-src/jellyfin.service | 2 +- .../fedora-package-x64/pkg-src/jellyfin.env | 19 +++++++++++++------ .../pkg-src/jellyfin.service | 2 +- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/deployment/debian-package-x64/pkg-src/conf/jellyfin b/deployment/debian-package-x64/pkg-src/conf/jellyfin index c237e2d692..b052b2ec66 100644 --- a/deployment/debian-package-x64/pkg-src/conf/jellyfin +++ b/deployment/debian-package-x64/pkg-src/conf/jellyfin @@ -19,13 +19,17 @@ JELLYFIN_LOG_DIRECTORY="/var/log/jellyfin" JELLYFIN_CACHE_DIRECTORY="/var/cache/jellyfin" # Restart script for in-app server control -JELLYFIN_RESTART_OPT="--restartpath /usr/lib/jellyfin/restart.sh" +JELLYFIN_RESTART_OPT="--restartpath=/usr/lib/jellyfin/restart.sh" -# [OPTIONAL] ffmpeg binary paths -#JELLYFIN_FFMPEG_OPTS="--ffmpeg /usr/bin/ffmpeg --ffprobe /usr/bin/ffprobe" +# [OPTIONAL] ffmpeg binary paths, overriding the UI-configured values +#JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/bin/ffmpeg" +#JELLYFIN_FFPROBE_OPT="--ffprobe=/usr/bin/ffprobe" -# [OPTIONAL] Additional user-defined options for the binary -#JELLYFIN_ADD_OPTS="" +# [OPTIONAL] run Jellyfin as a headless service +#JELLYFIN_SERVICE_OPT="--service" + +# [OPTIONAL] run Jellyfin without the web app +#JELLYFIN_NOWEBAPP_OPT="--noautorunwebapp" # # SysV init/Upstart options @@ -34,4 +38,4 @@ JELLYFIN_RESTART_OPT="--restartpath /usr/lib/jellyfin/restart.sh" # Application username JELLYFIN_USER="jellyfin" # Full application command -JELLYFIN_ARGS="--datadir $JELLYFIN_DATA_DIRECTORY --configdir $JELLYFIN_CONFIG_DIRECTORY --logdir $JELLYFIN_LOG_DIRECTORY --cachedir $JELLYFIN_CACHE_DIRECTORY $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPTS $JELLYFIN_ADD_OPTS" +JELLYFIN_ARGS="--datadir=$JELLYFIN_DATA_DIRECTORY --configdir=$JELLYFIN_CONFIG_DIRECTORY --logdir=$JELLYFIN_LOG_DIRECTORY --cachedir=$JELLYFIN_CACHE_DIRECTORY $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_FFPROBE_OPT $JELLYFIN_SERVICE_OPT $JELLFIN_NOWEBAPP_OPT" diff --git a/deployment/debian-package-x64/pkg-src/jellyfin.service b/deployment/debian-package-x64/pkg-src/jellyfin.service index ee89d7d4b1..9c6c6667f1 100644 --- a/deployment/debian-package-x64/pkg-src/jellyfin.service +++ b/deployment/debian-package-x64/pkg-src/jellyfin.service @@ -6,7 +6,7 @@ After = network.target Type = simple EnvironmentFile = /etc/default/jellyfin User = jellyfin -ExecStart = /usr/bin/jellyfin --datadir ${JELLYFIN_DATA_DIRECTORY} --configdir ${JELLYFIN_CONFIG_DIRECTORY} --logdir ${JELLYFIN_LOG_DIRECTORY} --cachedir ${JELLYFIN_CACHE_DIRECTORY} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPTS} ${JELLYFIN_ADD_OPTS} +ExecStart = /usr/bin/jellyfin --datadir=${JELLYFIN_DATA_DIRECTORY} --configdir=${JELLYFIN_CONFIG_DIRECTORY} --logdir=${JELLYFIN_LOG_DIRECTORY} --cachedir=${JELLYFIN_CACHE_DIRECTORY} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_FFPROBE_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} Restart = on-failure TimeoutSec = 15 diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.env b/deployment/fedora-package-x64/pkg-src/jellyfin.env index f7f041f756..11d5b0ab04 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.env +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.env @@ -14,15 +14,22 @@ # General options # -# Tell jellyfin wich ffmpeg/ffprobe to use -# JELLYFIN_FFMPEG="--ffmpeg /usr/bin/ffmpeg --ffprobe /usr/bin/ffprobe" - # Program directories JELLYFIN_DATA_DIRECTORY="/var/lib/jellyfin" JELLYFIN_CONFIG_DIRECTORY="/etc/jellyfin" JELLYFIN_LOG_DIRECTORY="/var/log/jellyfin" JELLYFIN_CACHE_DIRECTORY="/var/log/jellyfin" + # In-App service control -JELLYFIN_RESTART_OPT="--restartpath /usr/libexec/jellyfin/restart.sh" -# Additional options for the binary -JELLYFIN_ADD_OPTS="" +JELLYFIN_RESTART_OPT="--restartpath=/usr/libexec/jellyfin/restart.sh" + +# [OPTIONAL] ffmpeg binary paths, overriding the UI-configured values +#JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/bin/ffmpeg" +#JELLYFIN_FFPROBE_OPT="--ffprobe=/usr/bin/ffprobe" + +# [OPTIONAL] run Jellyfin as a headless service +#JELLYFIN_SERVICE_OPT="--service" + +# [OPTIONAL] run Jellyfin without the web app +#JELLYFIN_NOWEBAPP_OPT="--noautorunwebapp" + diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.service b/deployment/fedora-package-x64/pkg-src/jellyfin.service index 56703a98ac..d58df9d949 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.service +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.service @@ -5,7 +5,7 @@ Description=Jellyfin is a free software media system that puts you in control of [Service] EnvironmentFile=/etc/sysconfig/jellyfin WorkingDirectory=/var/lib/jellyfin -ExecStart=/usr/bin/jellyfin --datadir ${JELLYFIN_DATA_DIRECTORY} --configdir ${JELLYFIN_CONFIG_DIRECTORY} --logdir ${JELLYFIN_LOG_DIRECTORY} --cachedir ${JELLYFIN_CACHE_DIRECTORY} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_ADD_OPTS} ${JELLYFIN_FFMPEG} +ExecStart=/usr/bin/jellyfin --datadir=${JELLYFIN_DATA_DIRECTORY} --configdir=${JELLYFIN_CONFIG_DIRECTORY} --logdir=${JELLYFIN_LOG_DIRECTORY} --cachedir=${JELLYFIN_CACHE_DIRECTORY} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_FFPROBE_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} TimeoutSec=15 Restart=on-failure User=jellyfin From d351fa0c1e06862fd0cc7c68c0944ee0b8835809 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 3 Feb 2019 22:57:39 -0500 Subject: [PATCH 02/52] Make Fedora package.sh script use bash --- deployment/fedora-package-x64/package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/fedora-package-x64/package.sh b/deployment/fedora-package-x64/package.sh index d459cdb243..1d1329b03a 100755 --- a/deployment/fedora-package-x64/package.sh +++ b/deployment/fedora-package-x64/package.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env sh +#!/usr/bin/env bash source ../common.build.sh From f0e74c2c6b3fa8c94f0da222cde20a6b173db0b0 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Mon, 4 Feb 2019 00:26:23 -0500 Subject: [PATCH 03/52] Fix restart script for Debian --- .../debian-package-x64/pkg-src/bin/restart.sh | 10 ++++---- .../pkg-src/conf/jellyfin-sudoers | 24 +++++++++---------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/deployment/debian-package-x64/pkg-src/bin/restart.sh b/deployment/debian-package-x64/pkg-src/bin/restart.sh index a6f4632ba7..738f86727c 100644 --- a/deployment/debian-package-x64/pkg-src/bin/restart.sh +++ b/deployment/debian-package-x64/pkg-src/bin/restart.sh @@ -2,10 +2,12 @@ NAME=jellyfin -restart_cmds=("s6-svc -t /var/run/s6/services/${NAME}" \ - "systemctl restart ${NAME}" \ - "service ${NAME} restart" \ - "/etc/init.d/${NAME} restart") +restart_cmds=( + "systemctl restart ${NAME}" + "service ${NAME} restart" + "/etc/init.d/${NAME} restart" + "s6-svc -t /var/run/s6/services/${NAME}" +) for restart_cmd in "${restart_cmds[@]}"; do cmd=$(echo "$restart_cmd" | awk '{print $1}') diff --git a/deployment/debian-package-x64/pkg-src/conf/jellyfin-sudoers b/deployment/debian-package-x64/pkg-src/conf/jellyfin-sudoers index 4eb91366b4..b481ba4ad4 100644 --- a/deployment/debian-package-x64/pkg-src/conf/jellyfin-sudoers +++ b/deployment/debian-package-x64/pkg-src/conf/jellyfin-sudoers @@ -10,15 +10,15 @@ Cmnd_Alias STARTSERVER_INITD = /etc/init.d/jellyfin start Cmnd_Alias STOPSERVER_INITD = /etc/init.d/jellyfin stop -%jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSV -%jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSV -%jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSV -%jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSTEMD -%jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSTEMD -%jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSTEMD -%jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_INITD -%jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_INITD -%jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_INITD +jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSV +jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSV +jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSV +jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSTEMD +jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSTEMD +jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSTEMD +jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_INITD +jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_INITD +jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_INITD Defaults!RESTARTSERVER_SYSV !requiretty Defaults!STARTSERVER_SYSV !requiretty @@ -31,7 +31,7 @@ Defaults!STARTSERVER_INITD !requiretty Defaults!STOPSERVER_INITD !requiretty #Allow the server to mount iso images -%jellyfin ALL=(ALL) NOPASSWD: /bin/mount -%jellyfin ALL=(ALL) NOPASSWD: /bin/umount +jellyfin ALL=(ALL) NOPASSWD: /bin/mount +jellyfin ALL=(ALL) NOPASSWD: /bin/umount -Defaults:%jellyfin !requiretty +Defaults:jellyfin !requiretty From 72beadc74d524a5878feddb1769faab9296d38f7 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Mon, 4 Feb 2019 00:27:00 -0500 Subject: [PATCH 04/52] Fix restart script for Fedora/CentOS --- .../fedora-package-x64/pkg-src/jellyfin.sudoers | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.sudoers b/deployment/fedora-package-x64/pkg-src/jellyfin.sudoers index b31d52f7ec..ddd890ce16 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.sudoers +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.sudoers @@ -4,16 +4,16 @@ Cmnd_Alias STARTSERVER_SYSTEMD = /usr/bin/systemctl start jellyfin, /bin/systemc Cmnd_Alias STOPSERVER_SYSTEMD = /usr/bin/systemctl stop jellyfin, /bin/systemctl stop jellyfin -%jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSTEMD -%jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSTEMD -%jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSTEMD +jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSTEMD +jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSTEMD +jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSTEMD Defaults!RESTARTSERVER_SYSTEMD !requiretty Defaults!STARTSERVER_SYSTEMD !requiretty Defaults!STOPSERVER_SYSTEMD !requiretty # Uncomment to allow the server to mount iso images -# %jellyfin ALL=(ALL) NOPASSWD: /bin/mount -# %jellyfin ALL=(ALL) NOPASSWD: /bin/umount +# jellyfin ALL=(ALL) NOPASSWD: /bin/mount +# jellyfin ALL=(ALL) NOPASSWD: /bin/umount -Defaults:%jellyfin !requiretty +Defaults:jellyfin !requiretty From 546f4cd46f21561d9f39ee695fdfa6809e0d9c29 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Tue, 5 Feb 2019 18:40:31 -0500 Subject: [PATCH 05/52] Add prerequisite keep_artifacts var --- build | 23 +++++++++++++++++------ deployment/README.md | 2 ++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/build b/build index 7d49db72ab..7413b60e36 100755 --- a/build +++ b/build @@ -23,7 +23,7 @@ usage() { echo -e "Usage:" echo -e " $ build --list-platforms" echo -e " $ build --list-actions " - echo -e " $ build [-b/--web-branch ] " + echo -e " $ build [-k/--keep-artifacts] [-b/--web-branch ] " echo -e "" echo -e "The web_branch defaults to the same branch name as the current main branch." echo -e "To build all platforms, use 'all'." @@ -67,6 +67,14 @@ if [[ $1 == '--list-actions' ]]; then exit 0 fi +# Parse keep-artifacts option +if [[ $1 == '-k' || $1 == '--keep-artifacts' ]]; then + keep_artifacts="y" + shift 1 +else + keep_artifacts="n" +fi + # Parse branch option if [[ $1 == '-b' || $1 == '--web-branch' ]]; then web_branch="$2" @@ -193,6 +201,13 @@ for target_platform in ${platform[@]}; do echo -e "> Processing platform ${target_platform}" date_start=$( date +%s ) pushd ${target_platform} + cleanup() { + echo -e ">> Processing action clean" + if [[ -f clean.sh && -x clean.sh ]]; then + ./clean.sh ${keep_artifacts} + fi + } + trap cleanup EXIT INT for target_action in ${action[@]}; do echo -e ">> Processing action ${target_action}" if [[ -f ${target_action}.sh && -x ${target_action}.sh ]]; then @@ -204,12 +219,8 @@ for target_platform in ${platform[@]}; do target_dir="../../../jellyfin-build/${target_platform}" mkdir -p ${target_dir} mv pkg-dist/* ${target_dir}/ - - echo -e ">> Processing action clean" - if [[ -f clean.sh && -x clean.sh ]]; then - ./clean.sh - fi fi + cleanup date_end=$( date +%s ) echo -e "> Completed platform ${target_platform} in $( expr ${date_end} - ${date_start} ) seconds." popd diff --git a/deployment/README.md b/deployment/README.md index 05b4ed51e9..608d80e58f 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -55,6 +55,8 @@ These builds are not necessarily run from the `build` script, but are present fo * The `clean` action should always `exit 0` even if no work is done or it fails. +* The `clean` action can be passed a variable as argument 1, named `keep_artifacts`. It is indended to handle situations when the user runs `build --keep-artifacts` and should be handled intelligently. Usually, this is used to preserve Docker images while still removing temporary directories. + ### Output Files * Upon completion of the defined actions, at least one output file must be created in the `/pkg-dist` directory. From c846da4f9e7833252f209b2698c3b6359b20069d Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Tue, 5 Feb 2019 18:40:50 -0500 Subject: [PATCH 06/52] Update Debian package build --- deployment/debian-package-x64/Dockerfile | 39 ++++++++++----- deployment/debian-package-x64/clean.sh | 27 +++++++++- deployment/debian-package-x64/docker-build.sh | 16 ++++++ deployment/debian-package-x64/package.sh | 49 ++++++++++--------- 4 files changed, 94 insertions(+), 37 deletions(-) create mode 100755 deployment/debian-package-x64/docker-build.sh diff --git a/deployment/debian-package-x64/Dockerfile b/deployment/debian-package-x64/Dockerfile index 2afe6c1d37..787f1d1824 100644 --- a/deployment/debian-package-x64/Dockerfile +++ b/deployment/debian-package-x64/Dockerfile @@ -1,23 +1,40 @@ FROM debian:9 -ARG SOURCEDIR=/repo +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-x64 +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=2.2 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist ENV DEB_BUILD_OPTIONS=noddebs -# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current +# Prepare Debian build environment RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget devscripts \ - && wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg \ + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev + +# Install dotnet repository +# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current +RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg \ && mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/ \ && wget -q https://packages.microsoft.com/config/debian/9/prod.list \ && mv prod.list /etc/apt/sources.list.d/microsoft-prod.list \ && chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg \ && chown root:root /etc/apt/sources.list.d/microsoft-prod.list \ - && apt-get update + && apt-get update \ + && apt-get install -y dotnet-sdk-${SDK_VERSION} -WORKDIR ${SOURCEDIR} -COPY . . -COPY ./deployment/debian-package-x64/pkg-src ./debian +# Link to docker-build script +RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh -RUN yes | mk-build-deps -i debian/control \ - && dpkg-buildpackage -us -uc +# Link to Debian source dir; mkdir needed or it fails, can't force dest +RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian -WORKDIR / +# Prepare artifact volume +VOLUME ${ARTIFACT_DIR}/ + +# Copy repository +COPY . ${SOURCE_DIR}/ + +# Set docker-build entrypoint +ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/debian-package-x64/clean.sh b/deployment/debian-package-x64/clean.sh index 3df2d7796e..101ba1fb6d 100755 --- a/deployment/debian-package-x64/clean.sh +++ b/deployment/debian-package-x64/clean.sh @@ -2,6 +2,29 @@ source ../common.build.sh -VERSION=`get_version ../..` +keep_artifacts="${1}" -clean_jellyfin ../.. Release `pwd`/dist/jellyfin_${VERSION} +WORKDIR="$( pwd )" +VERSION="$( grep '^jellyfin' ${WORKDIR}/pkg-src/changelog | head -1 | awk -F '[()]' '{ print $2 }' )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-debian-build" + +rm -rf "${package_temporary_dir}" &>/dev/null \ + || sudo rm -rf "${package_temporary_dir}" &>/dev/null + +rm -rf "${output_dir}" &>/dev/null \ + || sudo rm -rf "${output_dir}" &>/dev/null + +if [[ ${keep_artifacts} == 'n' ]]; then + docker_sudo="" + if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo=sudo + fi + ${docker_sudo} docker image rm ${image_name} --force +fi diff --git a/deployment/debian-package-x64/docker-build.sh b/deployment/debian-package-x64/docker-build.sh new file mode 100755 index 0000000000..83654b8ce8 --- /dev/null +++ b/deployment/debian-package-x64/docker-build.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Builds the DEB inside the Docker container + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +# Build DEB +dpkg-buildpackage -us -uc + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/deb +mv /jellyfin_* ${ARTIFACT_DIR}/deb/ diff --git a/deployment/debian-package-x64/package.sh b/deployment/debian-package-x64/package.sh index dec953961f..11da66aaab 100755 --- a/deployment/debian-package-x64/package.sh +++ b/deployment/debian-package-x64/package.sh @@ -2,30 +2,31 @@ source ../common.build.sh -VERSION=`get_version ../..` +WORKDIR="$( pwd )" +VERSION="$( grep '^jellyfin' ${WORKDIR}/pkg-src/changelog | head -1 | awk -F '[()]' '{ print $2 }' )" -# TODO get the version in the package automatically. And using the changelog to decide the debian package suffix version. +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-debian-build" -# Build a Jellyfin .deb file with Docker on Linux -# Places the output .deb file in the parent directory +# Determine if sudo should be used for Docker +if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo="sudo" +else + docker_sudo="" +fi -package_temporary_dir="`pwd`/pkg-dist-tmp" -output_dir="`pwd`/pkg-dist" -current_user="`whoami`" -image_name="jellyfin-debuild" - -cleanup() { - set +o errexit - docker image rm $image_name --force - rm -rf "$package_temporary_dir" -} -trap cleanup EXIT INT - -docker build ../.. -t "$image_name" -f ./Dockerfile --build-arg SOURCEDIR="/jellyfin-${VERSION}" -mkdir -p "$package_temporary_dir" -mkdir -p "$output_dir" -docker run --rm -v "$package_temporary_dir:/temp" "$image_name" sh -c 'find / -maxdepth 1 -type f -name "jellyfin*" -exec mv {} /temp \;' -chown -R "$current_user" "$package_temporary_dir" \ -|| sudo chown -R "$current_user" "$package_temporary_dir" - -mv "$package_temporary_dir"/* "$output_dir" +# Set up the build environment Docker image +${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile +# Build the DEBs and copy out to ${package_temporary_dir} +${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" +# Correct ownership on the DEBs (as current user, then as root if that fails) +chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \ + || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null +# Move the DEBs to the output directory +mkdir -p "${output_dir}" +mv "${package_temporary_dir}"/deb/* "${output_dir}" From 88038d96448d57601e619572ed77d8c2f0fa4db9 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Tue, 5 Feb 2019 18:41:02 -0500 Subject: [PATCH 07/52] Update Fedora package build --- deployment/fedora-package-x64/Dockerfile | 45 ++++++++--- deployment/fedora-package-x64/clean.sh | 40 +++++++--- deployment/fedora-package-x64/docker-build.sh | 20 +++++ deployment/fedora-package-x64/package.sh | 74 +++++++++---------- 4 files changed, 115 insertions(+), 64 deletions(-) create mode 100755 deployment/fedora-package-x64/docker-build.sh diff --git a/deployment/fedora-package-x64/Dockerfile b/deployment/fedora-package-x64/Dockerfile index e5deac29fb..7decba2e98 100644 --- a/deployment/fedora-package-x64/Dockerfile +++ b/deployment/fedora-package-x64/Dockerfile @@ -1,15 +1,36 @@ FROM fedora:29 -ARG HOME=/build -RUN mkdir /build && \ - dnf install -y @buildsys-build rpmdevtools dnf-plugins-core && \ - dnf copr enable -y @dotnet-sig/dotnet && \ - rpmdev-setuptree +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG PLATFORM_DIR=/jellyfin/deployment/fedora-package-x64 +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=2.2 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist -WORKDIR /build/rpmbuild -COPY ./deployment/fedora-package-x64/pkg-src/jellyfin.spec SPECS -COPY ./deployment/fedora-package-x64/pkg-src/ SOURCES +# Prepare Fedora build environment +RUN dnf update -y \ + && dnf install -y @buildsys-build rpmdevtools dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel \ + && dnf copr enable -y @dotnet-sig/dotnet \ + && rpmdev-setuptree \ + && dnf install -y dotnet-sdk-${SDK_VERSION} -RUN spectool -g -R SPECS/jellyfin.spec && \ - rpmbuild -bs SPECS/jellyfin.spec && \ - dnf build-dep -y SRPMS/jellyfin-*.src.rpm && \ - rpmbuild -bb SPECS/jellyfin.spec; \ No newline at end of file +# Link to docker-build script +RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh + +# Link to RPM spec; mkdir needed or it fails, can't force dest +RUN mkdir -p ${SOURCE_DIR}/SPECS \ + && ln -s ${PLATFORM_DIR}/pkg-src/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec + +# Link to RPM sources; mkdir needed or it fails, can't force dest +RUN mkdir -p ${SOURCE_DIR}/SOURCES \ + && ln -s ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/SOURCES + +# Prepare artifact volume +VOLUME ${ARTIFACT_DIR}/ + +# Copy repository +COPY . ${SOURCE_DIR}/ + +# Set docker-build entrypoint +ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/fedora-package-x64/clean.sh b/deployment/fedora-package-x64/clean.sh index d7233208ff..fbd4d276d6 100755 --- a/deployment/fedora-package-x64/clean.sh +++ b/deployment/fedora-package-x64/clean.sh @@ -2,17 +2,33 @@ source ../common.build.sh -VERSION=`get_version ../..` +keep_artifacts="${1}" -package_temporary_dir="`pwd`/pkg-dist-tmp" -pkg_src_dir="`pwd`/pkg-src" -image_name="jellyfin-rpmbuild" -docker_sudo="" -if ! $(id -Gn | grep -q 'docker') && [ ! ${EUID:-1000} -eq 0 ] && \ - [ ! $USER == "root" ] && ! $(echo "$OSTYPE" | grep -q "darwin"); then - docker_sudo=sudo +WORKDIR="$( pwd )" +VERSION="$( grep -A1 '^Version:' ${WORKDIR}/pkg-src/jellyfin.spec | awk '{ print $NF }' )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +package_source_dir="${WORKDIR}/pkg-src" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-rpm-build" + +rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null \ + || sudo rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null + +rm -rf "${package_temporary_dir}" &>/dev/null \ + || sudo rm -rf "${package_temporary_dir}" &>/dev/null + +rm -rf "${output_dir}" &>/dev/null \ + || sudo rm -rf "${output_dir}" &>/dev/null + +if [[ ${keep_artifacts} == 'n' ]]; then + docker_sudo="" + if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo=sudo + fi + ${docker_sudo} docker image rm ${image_name} --force fi - -$docker_sudo docker image rm $image_name --force -rm -rf "$package_temporary_dir" -rm -rf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" diff --git a/deployment/fedora-package-x64/docker-build.sh b/deployment/fedora-package-x64/docker-build.sh new file mode 100755 index 0000000000..3acf1ec0df --- /dev/null +++ b/deployment/fedora-package-x64/docker-build.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Builds the RPM inside the Docker container + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +ls -al SOURCES/pkg-src/ + +# Build RPM +spectool -g -R SPECS/jellyfin.spec +rpmbuild -bs SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-src/" +rpmbuild -bb SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-src/" + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/rpm +mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/ diff --git a/deployment/fedora-package-x64/package.sh b/deployment/fedora-package-x64/package.sh index 1d1329b03a..088a41a35f 100755 --- a/deployment/fedora-package-x64/package.sh +++ b/deployment/fedora-package-x64/package.sh @@ -2,37 +2,28 @@ source ../common.build.sh -VERSION=`get_version ../..` +WORKDIR="$( pwd )" +VERSION="$( grep '^Version:' ${WORKDIR}/pkg-src/jellyfin.spec | awk '{ print $NF }' )" -# TODO get the version in the package automatically. And using the changelog to decide the debian package suffix version. +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +pkg_src_dir="${WORKDIR}/pkg-src" +current_user="$( whoami )" +image_name="jellyfin-rpm-build" -# Build a Jellyfin .rpm file with Docker on Linux -# Places the output .rpm file in the parent directory - -set -o errexit -set -o xtrace -set -o nounset - -package_temporary_dir="`pwd`/pkg-dist-tmp" -output_dir="`pwd`/pkg-dist" -pkg_src_dir="`pwd`/pkg-src" -current_user="`whoami`" -image_name="jellyfin-rpmbuild" -docker_sudo="" -if ! $(id -Gn | grep -q 'docker') && [ ! ${EUID:-1000} -eq 0 ] && \ - [ ! $USER == "root" ] && ! $(echo "$OSTYPE" | grep -q "darwin"); then - docker_sudo=sudo +# Determine if sudo should be used for Docker +if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo="sudo" +else + docker_sudo="" fi -cleanup() { - set +o errexit - $docker_sudo docker image rm $image_name --force - rm -rf "$package_temporary_dir" - rm -rf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" -} -trap cleanup EXIT INT +# Create RPM source archive GNU_TAR=1 -mkdir -p "$package_temporary_dir" +mkdir -p "${package_temporary_dir}" echo "Bundling all sources for RPM build." tar \ --transform "s,^\.,jellyfin-${VERSION}," \ @@ -47,12 +38,12 @@ tar \ --exclude='**/.nuget' \ --exclude='*.deb' \ --exclude='*.rpm' \ --zcf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" \ +-czf "${pkg_src_dir}/jellyfin-${VERSION}.tar.gz" \ -C "../.." ./ || GNU_TAR=0 if [ $GNU_TAR -eq 0 ]; then echo "The installed tar binary did not support --transform. Using workaround." - mkdir -p "$package_temporary_dir/jellyfin-${VERSION}" + mkdir -p "${package_temporary_dir}/jellyfin" # Not GNU tar tar \ --exclude='.git*' \ @@ -67,20 +58,23 @@ if [ $GNU_TAR -eq 0 ]; then --exclude='*.deb' \ --exclude='*.rpm' \ -zcf \ - "$package_temporary_dir/jellyfin-${VERSION}/jellyfin.tar.gz" \ - -C "../.." \ - ./ + "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" \ + -C "../.." ./ echo "Extracting filtered package." - tar -xzf "$package_temporary_dir/jellyfin-${VERSION}/jellyfin.tar.gz" -C "$package_temporary_dir/jellyfin-${VERSION}" + tar -xzf "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}/jellyfin-${VERSION}" echo "Removing filtered package." - rm "$package_temporary_dir/jellyfin-${VERSION}/jellyfin.tar.gz" + rm -f "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" echo "Repackaging package into final tarball." - tar -zcf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" -C "$package_temporary_dir" "jellyfin-${VERSION}" + tar -czf "${pkg_src_dir}/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}" "jellyfin-${VERSION}" fi -$docker_sudo docker build ../.. -t "$image_name" -f ./Dockerfile -mkdir -p "$output_dir" -$docker_sudo docker run --rm -v "$package_temporary_dir:/temp" "$image_name" sh -c 'find /build/rpmbuild -maxdepth 3 -type f -name "jellyfin*.rpm" -exec mv {} /temp \;' -chown -R "$current_user" "$package_temporary_dir" \ -|| sudo chown -R "$current_user" "$package_temporary_dir" -mv "$package_temporary_dir"/*.rpm "$output_dir" +# Set up the build environment Docker image +${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile +# Build the RPMs and copy out to ${package_temporary_dir} +${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" +# Correct ownership on the RPMs (as current user, then as root if that fails) +chown -R "${current_user}" "${package_temporary_dir}" \ + || sudo chown -R "${current_user}" "${package_temporary_dir}" +# Move the RPMs to the output directory +mkdir -p "${output_dir}" +mv "${package_temporary_dir}"/rpm/* "${output_dir}" From 8413c56392eb331bc319cddf135b671ec0a80123 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Tue, 5 Feb 2019 19:04:20 -0500 Subject: [PATCH 08/52] Update CentOS package build --- deployment/centos-package-x64/Dockerfile | 45 ++++++++++++++----- .../centos-package-x64/dependencies.txt | 1 + deployment/centos-package-x64/docker-build.sh | 1 + 3 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 deployment/centos-package-x64/dependencies.txt create mode 120000 deployment/centos-package-x64/docker-build.sh diff --git a/deployment/centos-package-x64/Dockerfile b/deployment/centos-package-x64/Dockerfile index bbe5ffd277..89799bca20 100644 --- a/deployment/centos-package-x64/Dockerfile +++ b/deployment/centos-package-x64/Dockerfile @@ -1,15 +1,36 @@ FROM centos:7 -ARG HOME=/build -RUN mkdir /build && \ - yum install -y @buildsys-build rpmdevtools yum-plugins-core && \ - rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm && \ - rpmdev-setuptree +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG PLATFORM_DIR=/jellyfin/deployment/centos-package-x64 +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=2.2 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist -WORKDIR /build/rpmbuild -COPY ./deployment/centos-package-x64/pkg-src/jellyfin.spec SPECS -COPY ./deployment/centos-package-x64/pkg-src/ SOURCES +# Prepare CentOS build environment +RUN yum update -y \ + && yum install -y @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel \ + && rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm \ + && rpmdev-setuptree \ + && yum install -y dotnet-sdk-${SDK_VERSION} -RUN spectool -g -R SPECS/jellyfin.spec && \ - rpmbuild -bs SPECS/jellyfin.spec && \ - yum-builddep -y SRPMS/jellyfin-*.src.rpm && \ - rpmbuild -bb SPECS/jellyfin.spec; \ No newline at end of file +# Link to docker-build script +RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh + +# Link to RPM spec; mkdir needed or it fails, can't force dest +RUN mkdir -p ${SOURCE_DIR}/SPECS \ + && ln -s ${PLATFORM_DIR}/pkg-src/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec + +# Link to RPM sources; mkdir needed or it fails, can't force dest +RUN mkdir -p ${SOURCE_DIR}/SOURCES \ + && ln -s ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/SOURCES + +# Prepare artifact volume +VOLUME ${ARTIFACT_DIR}/ + +# Copy repository +COPY . ${SOURCE_DIR}/ + +# Set docker-build entrypoint +ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/centos-package-x64/dependencies.txt b/deployment/centos-package-x64/dependencies.txt new file mode 100644 index 0000000000..bdb9670965 --- /dev/null +++ b/deployment/centos-package-x64/dependencies.txt @@ -0,0 +1 @@ +docker diff --git a/deployment/centos-package-x64/docker-build.sh b/deployment/centos-package-x64/docker-build.sh new file mode 120000 index 0000000000..7adc32954d --- /dev/null +++ b/deployment/centos-package-x64/docker-build.sh @@ -0,0 +1 @@ +../fedora-package-x64/docker-build.sh \ No newline at end of file From ceb8b9f74014060417ed55680038a2601c5e366a Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Tue, 5 Feb 2019 21:32:35 -0500 Subject: [PATCH 09/52] Add explanation to usage output --- build | 1 + 1 file changed, 1 insertion(+) diff --git a/build b/build index 7413b60e36..3b4167dae4 100755 --- a/build +++ b/build @@ -25,6 +25,7 @@ usage() { echo -e " $ build --list-actions " echo -e " $ build [-k/--keep-artifacts] [-b/--web-branch ] " echo -e "" + echo -e "The 'keep-artifacts' option preserves build artifacts, e.g. Docker images for system package builds." echo -e "The web_branch defaults to the same branch name as the current main branch." echo -e "To build all platforms, use 'all'." echo -e "To perform all build actions, use 'all'." From e1d523ee458d19ad412795b71d9dfb1c4fc7b688 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Tue, 5 Feb 2019 21:37:56 -0500 Subject: [PATCH 10/52] Improve description of keep_artifacts flag --- deployment/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/README.md b/deployment/README.md index 608d80e58f..a00cd3e6c2 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -55,7 +55,7 @@ These builds are not necessarily run from the `build` script, but are present fo * The `clean` action should always `exit 0` even if no work is done or it fails. -* The `clean` action can be passed a variable as argument 1, named `keep_artifacts`. It is indended to handle situations when the user runs `build --keep-artifacts` and should be handled intelligently. Usually, this is used to preserve Docker images while still removing temporary directories. +* The `clean` action can be passed a variable as argument 1, named `keep_artifacts`, containing either the value `y` or `n`. It is indended to handle situations when the user runs `build --keep-artifacts` and should be handled intelligently. Usually, this is used to preserve Docker images while still removing temporary directories. ### Output Files From b8b650540d604dce7f9066cae6765488f4138198 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Wed, 6 Feb 2019 09:33:14 -0500 Subject: [PATCH 11/52] Allow Fedora/CentOS mounting by default --- deployment/fedora-package-x64/pkg-src/jellyfin.sudoers | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.sudoers b/deployment/fedora-package-x64/pkg-src/jellyfin.sudoers index ddd890ce16..dd245af4b8 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.sudoers +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.sudoers @@ -12,8 +12,8 @@ Defaults!RESTARTSERVER_SYSTEMD !requiretty Defaults!STARTSERVER_SYSTEMD !requiretty Defaults!STOPSERVER_SYSTEMD !requiretty -# Uncomment to allow the server to mount iso images -# jellyfin ALL=(ALL) NOPASSWD: /bin/mount -# jellyfin ALL=(ALL) NOPASSWD: /bin/umount +# Allow the server to mount iso images +jellyfin ALL=(ALL) NOPASSWD: /bin/mount +jellyfin ALL=(ALL) NOPASSWD: /bin/umount Defaults:jellyfin !requiretty From da860e6e54bb938741d264c58569f809129b3792 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Fri, 8 Feb 2019 20:35:50 -0500 Subject: [PATCH 12/52] Remove superfluous VERSION variables --- deployment/debian-package-x64/clean.sh | 1 - deployment/debian-package-x64/package.sh | 1 - 2 files changed, 2 deletions(-) diff --git a/deployment/debian-package-x64/clean.sh b/deployment/debian-package-x64/clean.sh index 101ba1fb6d..b2960fcb3a 100755 --- a/deployment/debian-package-x64/clean.sh +++ b/deployment/debian-package-x64/clean.sh @@ -5,7 +5,6 @@ source ../common.build.sh keep_artifacts="${1}" WORKDIR="$( pwd )" -VERSION="$( grep '^jellyfin' ${WORKDIR}/pkg-src/changelog | head -1 | awk -F '[()]' '{ print $2 }' )" package_temporary_dir="${WORKDIR}/pkg-dist-tmp" output_dir="${WORKDIR}/pkg-dist" diff --git a/deployment/debian-package-x64/package.sh b/deployment/debian-package-x64/package.sh index 11da66aaab..d7c3f5809c 100755 --- a/deployment/debian-package-x64/package.sh +++ b/deployment/debian-package-x64/package.sh @@ -3,7 +3,6 @@ source ../common.build.sh WORKDIR="$( pwd )" -VERSION="$( grep '^jellyfin' ${WORKDIR}/pkg-src/changelog | head -1 | awk -F '[()]' '{ print $2 }' )" package_temporary_dir="${WORKDIR}/pkg-dist-tmp" output_dir="${WORKDIR}/pkg-dist" From 139e3c19ee1216ea68d9c1d79c2cb890faeb7f53 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Fri, 8 Feb 2019 20:42:30 -0500 Subject: [PATCH 13/52] Replace symlinks with copies and update image name --- deployment/centos-package-x64/clean.sh | 35 +++++++- deployment/centos-package-x64/docker-build.sh | 21 ++++- deployment/centos-package-x64/package.sh | 81 ++++++++++++++++++- 3 files changed, 134 insertions(+), 3 deletions(-) mode change 120000 => 100755 deployment/centos-package-x64/clean.sh mode change 120000 => 100755 deployment/centos-package-x64/docker-build.sh mode change 120000 => 100755 deployment/centos-package-x64/package.sh diff --git a/deployment/centos-package-x64/clean.sh b/deployment/centos-package-x64/clean.sh deleted file mode 120000 index d6d2d1c091..0000000000 --- a/deployment/centos-package-x64/clean.sh +++ /dev/null @@ -1 +0,0 @@ -../fedora-package-x64/clean.sh \ No newline at end of file diff --git a/deployment/centos-package-x64/clean.sh b/deployment/centos-package-x64/clean.sh new file mode 100755 index 0000000000..7278372e18 --- /dev/null +++ b/deployment/centos-package-x64/clean.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +source ../common.build.sh + +keep_artifacts="${1}" + +WORKDIR="$( pwd )" +VERSION="$( grep -A1 '^Version:' ${WORKDIR}/pkg-src/jellyfin.spec | awk '{ print $NF }' )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +package_source_dir="${WORKDIR}/pkg-src" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-centos-build" + +rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null \ + || sudo rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null + +rm -rf "${package_temporary_dir}" &>/dev/null \ + || sudo rm -rf "${package_temporary_dir}" &>/dev/null + +rm -rf "${output_dir}" &>/dev/null \ + || sudo rm -rf "${output_dir}" &>/dev/null + +if [[ ${keep_artifacts} == 'n' ]]; then + docker_sudo="" + if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo=sudo + fi + ${docker_sudo} docker image rm ${image_name} --force +fi diff --git a/deployment/centos-package-x64/docker-build.sh b/deployment/centos-package-x64/docker-build.sh deleted file mode 120000 index 7adc32954d..0000000000 --- a/deployment/centos-package-x64/docker-build.sh +++ /dev/null @@ -1 +0,0 @@ -../fedora-package-x64/docker-build.sh \ No newline at end of file diff --git a/deployment/centos-package-x64/docker-build.sh b/deployment/centos-package-x64/docker-build.sh new file mode 100755 index 0000000000..3acf1ec0df --- /dev/null +++ b/deployment/centos-package-x64/docker-build.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Builds the RPM inside the Docker container + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +ls -al SOURCES/pkg-src/ + +# Build RPM +spectool -g -R SPECS/jellyfin.spec +rpmbuild -bs SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-src/" +rpmbuild -bb SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-src/" + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/rpm +mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/ diff --git a/deployment/centos-package-x64/package.sh b/deployment/centos-package-x64/package.sh deleted file mode 120000 index a79de21eba..0000000000 --- a/deployment/centos-package-x64/package.sh +++ /dev/null @@ -1 +0,0 @@ -../fedora-package-x64/package.sh \ No newline at end of file diff --git a/deployment/centos-package-x64/package.sh b/deployment/centos-package-x64/package.sh new file mode 100755 index 0000000000..27d686e46f --- /dev/null +++ b/deployment/centos-package-x64/package.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +source ../common.build.sh + +WORKDIR="$( pwd )" +VERSION="$( grep '^Version:' ${WORKDIR}/pkg-src/jellyfin.spec | awk '{ print $NF }' )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +pkg_src_dir="${WORKDIR}/pkg-src" +current_user="$( whoami )" +image_name="jellyfin-centos-build" + +# Determine if sudo should be used for Docker +if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo="sudo" +else + docker_sudo="" +fi + +# Create RPM source archive +GNU_TAR=1 +mkdir -p "${package_temporary_dir}" +echo "Bundling all sources for RPM build." +tar \ +--transform "s,^\.,jellyfin-${VERSION}," \ +--exclude='.git*' \ +--exclude='**/.git' \ +--exclude='**/.hg' \ +--exclude='**/.vs' \ +--exclude='**/.vscode' \ +--exclude='deployment' \ +--exclude='**/bin' \ +--exclude='**/obj' \ +--exclude='**/.nuget' \ +--exclude='*.deb' \ +--exclude='*.rpm' \ +-czf "${pkg_src_dir}/jellyfin-${VERSION}.tar.gz" \ +-C "../.." ./ || GNU_TAR=0 + +if [ $GNU_TAR -eq 0 ]; then + echo "The installed tar binary did not support --transform. Using workaround." + mkdir -p "${package_temporary_dir}/jellyfin" + # Not GNU tar + tar \ + --exclude='.git*' \ + --exclude='**/.git' \ + --exclude='**/.hg' \ + --exclude='**/.vs' \ + --exclude='**/.vscode' \ + --exclude='deployment' \ + --exclude='**/bin' \ + --exclude='**/obj' \ + --exclude='**/.nuget' \ + --exclude='*.deb' \ + --exclude='*.rpm' \ + -zcf \ + "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" \ + -C "../.." ./ + echo "Extracting filtered package." + tar -xzf "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}/jellyfin-${VERSION}" + echo "Removing filtered package." + rm -f "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" + echo "Repackaging package into final tarball." + tar -czf "${pkg_src_dir}/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}" "jellyfin-${VERSION}" +fi + +# Set up the build environment Docker image +${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile +# Build the RPMs and copy out to ${package_temporary_dir} +${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" +# Correct ownership on the RPMs (as current user, then as root if that fails) +chown -R "${current_user}" "${package_temporary_dir}" \ + || sudo chown -R "${current_user}" "${package_temporary_dir}" +# Move the RPMs to the output directory +mkdir -p "${output_dir}" +mv "${package_temporary_dir}"/rpm/* "${output_dir}" From 37985c2e260a56c1512a56726afc136f264d8bbc Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Fri, 8 Feb 2019 20:44:41 -0500 Subject: [PATCH 14/52] Update image name --- deployment/fedora-package-x64/clean.sh | 2 +- deployment/fedora-package-x64/package.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment/fedora-package-x64/clean.sh b/deployment/fedora-package-x64/clean.sh index fbd4d276d6..408167e49b 100755 --- a/deployment/fedora-package-x64/clean.sh +++ b/deployment/fedora-package-x64/clean.sh @@ -11,7 +11,7 @@ package_temporary_dir="${WORKDIR}/pkg-dist-tmp" package_source_dir="${WORKDIR}/pkg-src" output_dir="${WORKDIR}/pkg-dist" current_user="$( whoami )" -image_name="jellyfin-rpm-build" +image_name="jellyfin-fedora-build" rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null \ || sudo rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null diff --git a/deployment/fedora-package-x64/package.sh b/deployment/fedora-package-x64/package.sh index 088a41a35f..74586417d9 100755 --- a/deployment/fedora-package-x64/package.sh +++ b/deployment/fedora-package-x64/package.sh @@ -9,7 +9,7 @@ package_temporary_dir="${WORKDIR}/pkg-dist-tmp" output_dir="${WORKDIR}/pkg-dist" pkg_src_dir="${WORKDIR}/pkg-src" current_user="$( whoami )" -image_name="jellyfin-rpm-build" +image_name="jellyfin-fedora-build" # Determine if sudo should be used for Docker if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ From 3014866f65eb1573e189867b42f8892fc54a2019 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Fri, 8 Feb 2019 21:08:34 -0500 Subject: [PATCH 15/52] Add similar Ubuntu build package --- deployment/ubuntu-package-x64/Dockerfile | 40 +++++++++++++++++++ deployment/ubuntu-package-x64/clean.sh | 29 ++++++++++++++ .../ubuntu-package-x64/dependencies.txt | 1 + deployment/ubuntu-package-x64/docker-build.sh | 16 ++++++++ deployment/ubuntu-package-x64/package.sh | 31 ++++++++++++++ deployment/ubuntu-package-x64/pkg-src | 1 + 6 files changed, 118 insertions(+) create mode 100644 deployment/ubuntu-package-x64/Dockerfile create mode 100755 deployment/ubuntu-package-x64/clean.sh create mode 100644 deployment/ubuntu-package-x64/dependencies.txt create mode 100755 deployment/ubuntu-package-x64/docker-build.sh create mode 100755 deployment/ubuntu-package-x64/package.sh create mode 120000 deployment/ubuntu-package-x64/pkg-src diff --git a/deployment/ubuntu-package-x64/Dockerfile b/deployment/ubuntu-package-x64/Dockerfile new file mode 100644 index 0000000000..471f50c27d --- /dev/null +++ b/deployment/ubuntu-package-x64/Dockerfile @@ -0,0 +1,40 @@ +FROM ubuntu:18.04 +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-x64 +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=2.2 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV DEB_BUILD_OPTIONS=noddebs + +# Prepare Debian build environment +RUN apt-get update \ + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev + +# Install dotnet repository +# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current +RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg \ + && mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/ \ + && wget -q https://packages.microsoft.com/config/ubuntu/18.04/prod.list \ + && mv prod.list /etc/apt/sources.list.d/microsoft-prod.list \ + && chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg \ + && chown root:root /etc/apt/sources.list.d/microsoft-prod.list \ + && apt-get update \ + && apt-get install -y dotnet-sdk-${SDK_VERSION} + +# Link to docker-build script +RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh + +# Link to Debian source dir; mkdir needed or it fails, can't force dest +RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian + +# Prepare artifact volume +VOLUME ${ARTIFACT_DIR}/ + +# Copy repository +COPY . ${SOURCE_DIR}/ + +# Set docker-build entrypoint +ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/ubuntu-package-x64/clean.sh b/deployment/ubuntu-package-x64/clean.sh new file mode 100755 index 0000000000..c92c7fdec6 --- /dev/null +++ b/deployment/ubuntu-package-x64/clean.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +source ../common.build.sh + +keep_artifacts="${1}" + +WORKDIR="$( pwd )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-ubuntu-build" + +rm -rf "${package_temporary_dir}" &>/dev/null \ + || sudo rm -rf "${package_temporary_dir}" &>/dev/null + +rm -rf "${output_dir}" &>/dev/null \ + || sudo rm -rf "${output_dir}" &>/dev/null + +if [[ ${keep_artifacts} == 'n' ]]; then + docker_sudo="" + if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo=sudo + fi + ${docker_sudo} docker image rm ${image_name} --force +fi diff --git a/deployment/ubuntu-package-x64/dependencies.txt b/deployment/ubuntu-package-x64/dependencies.txt new file mode 100644 index 0000000000..bdb9670965 --- /dev/null +++ b/deployment/ubuntu-package-x64/dependencies.txt @@ -0,0 +1 @@ +docker diff --git a/deployment/ubuntu-package-x64/docker-build.sh b/deployment/ubuntu-package-x64/docker-build.sh new file mode 100755 index 0000000000..83654b8ce8 --- /dev/null +++ b/deployment/ubuntu-package-x64/docker-build.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Builds the DEB inside the Docker container + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +# Build DEB +dpkg-buildpackage -us -uc + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/deb +mv /jellyfin_* ${ARTIFACT_DIR}/deb/ diff --git a/deployment/ubuntu-package-x64/package.sh b/deployment/ubuntu-package-x64/package.sh new file mode 100755 index 0000000000..6d4625a197 --- /dev/null +++ b/deployment/ubuntu-package-x64/package.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +source ../common.build.sh + +WORKDIR="$( pwd )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-ubuntu-build" + +# Determine if sudo should be used for Docker +if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo="sudo" +else + docker_sudo="" +fi + +# Set up the build environment Docker image +${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile +# Build the DEBs and copy out to ${package_temporary_dir} +${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" +# Correct ownership on the DEBs (as current user, then as root if that fails) +chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \ + || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null +# Move the DEBs to the output directory +mkdir -p "${output_dir}" +mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/ubuntu-package-x64/pkg-src b/deployment/ubuntu-package-x64/pkg-src new file mode 120000 index 0000000000..4c695fea17 --- /dev/null +++ b/deployment/ubuntu-package-x64/pkg-src @@ -0,0 +1 @@ +../debian-package-x64/pkg-src \ No newline at end of file From a71040ba1b0f5f8ff78683971338704d9a9ff254 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 9 Feb 2019 21:28:36 +0100 Subject: [PATCH 16/52] Chnage to own NuGet package --- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index af01001a58..8356a9501b 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -25,8 +25,7 @@ - - + From bee8f58265d68177bb4ee9be2e2ba86f57a4c42a Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 9 Feb 2019 21:32:59 +0100 Subject: [PATCH 17/52] Upgrade package versions --- Jellyfin.Server/Jellyfin.Server.csproj | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 1f72de86d7..b1515df439 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -41,9 +41,8 @@ - - - + + From 74d2698c5fd7f70b2409afc5be0e9825c5edeeda Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sat, 9 Feb 2019 18:37:35 -0500 Subject: [PATCH 18/52] Fix poor handling of cache directories --- .../AppBase/BaseConfigurationManager.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index 460809e936..cf07cb1ecf 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -171,16 +171,29 @@ namespace Emby.Server.Implementations.AppBase private void UpdateCachePath() { string cachePath; - + // If the configuration file has no entry (i.e. not set in UI) if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath)) { - cachePath = null; + // If the current live configuration has no entry (i.e. not set on CLI/envvars, during startup) + if (string.IsNullOrWhiteSpace(((BaseApplicationPaths)CommonApplicationPaths).CachePath)) + { + // Set cachePath to a default value under ProgramDataPath + cachePath = (((BaseApplicationPaths)CommonApplicationPaths).ProgramDataPath + "/cache"); + } + else + { + // Set cachePath to the existing live value; will require restart if UI value is removed (but not replaced) + // TODO: Figure out how to re-grab this from the CLI/envvars while running + cachePath = ((BaseApplicationPaths)CommonApplicationPaths).CachePath; + } } else { - cachePath = Path.Combine(CommonConfiguration.CachePath, "cache"); + // Set cachePath to the new UI-set value + cachePath = CommonConfiguration.CachePath; } + Logger.LogInformation("Setting cache path to " + cachePath); ((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath; } From 7be4a8500c29d58dff9af5a3bf85edeb5d6b73b6 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sat, 9 Feb 2019 19:02:27 -0500 Subject: [PATCH 19/52] Use Microsoft dotnet Docker images --- deployment/debian-package-x64/Dockerfile | 24 ++++------------------ deployment/ubuntu-package-x64/Dockerfile | 26 +++++------------------- 2 files changed, 9 insertions(+), 41 deletions(-) diff --git a/deployment/debian-package-x64/Dockerfile b/deployment/debian-package-x64/Dockerfile index 787f1d1824..da66c9f944 100644 --- a/deployment/debian-package-x64/Dockerfile +++ b/deployment/debian-package-x64/Dockerfile @@ -1,9 +1,8 @@ -FROM debian:9 +FROM microsoft/dotnet:2.2-sdk-stretch # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-x64 ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -11,24 +10,9 @@ ENV DEB_BUILD_OPTIONS=noddebs # Prepare Debian build environment RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev - -# Install dotnet repository -# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg \ - && mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/ \ - && wget -q https://packages.microsoft.com/config/debian/9/prod.list \ - && mv prod.list /etc/apt/sources.list.d/microsoft-prod.list \ - && chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg \ - && chown root:root /etc/apt/sources.list.d/microsoft-prod.list \ - && apt-get update \ - && apt-get install -y dotnet-sdk-${SDK_VERSION} - -# Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh - -# Link to Debian source dir; mkdir needed or it fails, can't force dest -RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev \ + && ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \ + && mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian # Prepare artifact volume VOLUME ${ARTIFACT_DIR}/ diff --git a/deployment/ubuntu-package-x64/Dockerfile b/deployment/ubuntu-package-x64/Dockerfile index 471f50c27d..047d6a7a2f 100644 --- a/deployment/ubuntu-package-x64/Dockerfile +++ b/deployment/ubuntu-package-x64/Dockerfile @@ -1,34 +1,18 @@ -FROM ubuntu:18.04 +FROM microsoft/dotnet:2.2-sdk-bionic # Docker build arguments ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-x64 ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist ENV DEB_BUILD_OPTIONS=noddebs -# Prepare Debian build environment +# Prepare Ubuntu build environment RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev - -# Install dotnet repository -# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg \ - && mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/ \ - && wget -q https://packages.microsoft.com/config/ubuntu/18.04/prod.list \ - && mv prod.list /etc/apt/sources.list.d/microsoft-prod.list \ - && chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg \ - && chown root:root /etc/apt/sources.list.d/microsoft-prod.list \ - && apt-get update \ - && apt-get install -y dotnet-sdk-${SDK_VERSION} - -# Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh - -# Link to Debian source dir; mkdir needed or it fails, can't force dest -RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev \ + && ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \ + && mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian # Prepare artifact volume VOLUME ${ARTIFACT_DIR}/ From 57cefb432a052eb3d10019cf219a0eb8a5dfe088 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sat, 9 Feb 2019 19:06:27 -0500 Subject: [PATCH 20/52] Combine various RUNs in Fedora/CentOS --- deployment/centos-package-x64/Dockerfile | 16 +++++----------- deployment/fedora-package-x64/Dockerfile | 16 +++++----------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/deployment/centos-package-x64/Dockerfile b/deployment/centos-package-x64/Dockerfile index 89799bca20..4cb288c84d 100644 --- a/deployment/centos-package-x64/Dockerfile +++ b/deployment/centos-package-x64/Dockerfile @@ -13,17 +13,11 @@ RUN yum update -y \ && yum install -y @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel \ && rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm \ && rpmdev-setuptree \ - && yum install -y dotnet-sdk-${SDK_VERSION} - -# Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh - -# Link to RPM spec; mkdir needed or it fails, can't force dest -RUN mkdir -p ${SOURCE_DIR}/SPECS \ - && ln -s ${PLATFORM_DIR}/pkg-src/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec - -# Link to RPM sources; mkdir needed or it fails, can't force dest -RUN mkdir -p ${SOURCE_DIR}/SOURCES \ + && yum install -y dotnet-sdk-${SDK_VERSION} \ + && ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \ + && mkdir -p ${SOURCE_DIR}/SPECS \ + && ln -s ${PLATFORM_DIR}/pkg-src/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \ + && mkdir -p ${SOURCE_DIR}/SOURCES \ && ln -s ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/SOURCES # Prepare artifact volume diff --git a/deployment/fedora-package-x64/Dockerfile b/deployment/fedora-package-x64/Dockerfile index 7decba2e98..944f489b51 100644 --- a/deployment/fedora-package-x64/Dockerfile +++ b/deployment/fedora-package-x64/Dockerfile @@ -13,17 +13,11 @@ RUN dnf update -y \ && dnf install -y @buildsys-build rpmdevtools dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel \ && dnf copr enable -y @dotnet-sig/dotnet \ && rpmdev-setuptree \ - && dnf install -y dotnet-sdk-${SDK_VERSION} - -# Link to docker-build script -RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh - -# Link to RPM spec; mkdir needed or it fails, can't force dest -RUN mkdir -p ${SOURCE_DIR}/SPECS \ - && ln -s ${PLATFORM_DIR}/pkg-src/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec - -# Link to RPM sources; mkdir needed or it fails, can't force dest -RUN mkdir -p ${SOURCE_DIR}/SOURCES \ + && dnf install -y dotnet-sdk-${SDK_VERSION} \ + && ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \ + && mkdir -p ${SOURCE_DIR}/SPECS \ + && ln -s ${PLATFORM_DIR}/pkg-src/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \ + && mkdir -p ${SOURCE_DIR}/SOURCES \ && ln -s ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/SOURCES # Prepare artifact volume From 00234a5ece42126ec5039aa243d4a679589be33e Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sat, 9 Feb 2019 19:12:55 -0500 Subject: [PATCH 21/52] Remove superfluous comments --- deployment/centos-package-x64/Dockerfile | 3 --- deployment/debian-package-x64/Dockerfile | 3 --- deployment/fedora-package-x64/Dockerfile | 3 --- deployment/ubuntu-package-x64/Dockerfile | 3 --- 4 files changed, 12 deletions(-) diff --git a/deployment/centos-package-x64/Dockerfile b/deployment/centos-package-x64/Dockerfile index 4cb288c84d..38853f1738 100644 --- a/deployment/centos-package-x64/Dockerfile +++ b/deployment/centos-package-x64/Dockerfile @@ -20,11 +20,8 @@ RUN yum update -y \ && mkdir -p ${SOURCE_DIR}/SOURCES \ && ln -s ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/SOURCES -# Prepare artifact volume VOLUME ${ARTIFACT_DIR}/ -# Copy repository COPY . ${SOURCE_DIR}/ -# Set docker-build entrypoint ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/debian-package-x64/Dockerfile b/deployment/debian-package-x64/Dockerfile index da66c9f944..9819cc20db 100644 --- a/deployment/debian-package-x64/Dockerfile +++ b/deployment/debian-package-x64/Dockerfile @@ -14,11 +14,8 @@ RUN apt-get update \ && ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \ && mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian -# Prepare artifact volume VOLUME ${ARTIFACT_DIR}/ -# Copy repository COPY . ${SOURCE_DIR}/ -# Set docker-build entrypoint ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/fedora-package-x64/Dockerfile b/deployment/fedora-package-x64/Dockerfile index 944f489b51..8bb1d527da 100644 --- a/deployment/fedora-package-x64/Dockerfile +++ b/deployment/fedora-package-x64/Dockerfile @@ -20,11 +20,8 @@ RUN dnf update -y \ && mkdir -p ${SOURCE_DIR}/SOURCES \ && ln -s ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/SOURCES -# Prepare artifact volume VOLUME ${ARTIFACT_DIR}/ -# Copy repository COPY . ${SOURCE_DIR}/ -# Set docker-build entrypoint ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/ubuntu-package-x64/Dockerfile b/deployment/ubuntu-package-x64/Dockerfile index 047d6a7a2f..485b6c42ce 100644 --- a/deployment/ubuntu-package-x64/Dockerfile +++ b/deployment/ubuntu-package-x64/Dockerfile @@ -14,11 +14,8 @@ RUN apt-get update \ && ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \ && mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian -# Prepare artifact volume VOLUME ${ARTIFACT_DIR}/ -# Copy repository COPY . ${SOURCE_DIR}/ -# Set docker-build entrypoint ENTRYPOINT ["/docker-build.sh"] From 382b8bb509bbce34354a5d667c755b91d52c4fa4 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sat, 9 Feb 2019 19:14:34 -0500 Subject: [PATCH 22/52] Use Path.Combine --- Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index cf07cb1ecf..5feac1adf9 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -178,7 +178,7 @@ namespace Emby.Server.Implementations.AppBase if (string.IsNullOrWhiteSpace(((BaseApplicationPaths)CommonApplicationPaths).CachePath)) { // Set cachePath to a default value under ProgramDataPath - cachePath = (((BaseApplicationPaths)CommonApplicationPaths).ProgramDataPath + "/cache"); + cachePath = Path.Combine(((BaseApplicationPaths)CommonApplicationPaths).ProgramDataPath, "cache"); } else { From fb256b7aa0cf500c54fd1a1fa8295a0dac9520b1 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sat, 9 Feb 2019 19:32:06 -0500 Subject: [PATCH 23/52] Fix control file for Microsoft Docker setup --- deployment/debian-package-x64/docker-build.sh | 3 +++ deployment/ubuntu-package-x64/docker-build.sh | 3 +++ 2 files changed, 6 insertions(+) diff --git a/deployment/debian-package-x64/docker-build.sh b/deployment/debian-package-x64/docker-build.sh index 83654b8ce8..0590be0972 100755 --- a/deployment/debian-package-x64/docker-build.sh +++ b/deployment/debian-package-x64/docker-build.sh @@ -8,6 +8,9 @@ set -o xtrace # Move to source directory pushd ${SOURCE_DIR} +# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image +sed -i '/dotnet-sdk-2.2,/d' debian/control + # Build DEB dpkg-buildpackage -us -uc diff --git a/deployment/ubuntu-package-x64/docker-build.sh b/deployment/ubuntu-package-x64/docker-build.sh index 83654b8ce8..0590be0972 100755 --- a/deployment/ubuntu-package-x64/docker-build.sh +++ b/deployment/ubuntu-package-x64/docker-build.sh @@ -8,6 +8,9 @@ set -o xtrace # Move to source directory pushd ${SOURCE_DIR} +# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image +sed -i '/dotnet-sdk-2.2,/d' debian/control + # Build DEB dpkg-buildpackage -us -uc From 86089ec03a4af121281154a6159cc70787571dbe Mon Sep 17 00:00:00 2001 From: Felix Ruhnow Date: Sun, 10 Feb 2019 22:23:47 +0100 Subject: [PATCH 24/52] Adding support for "tvg-chno"-Tag in M3U Channel Lists --- .../LiveTv/TunerHosts/M3uParser.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 9a01c42d3b..e10c445019 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -204,6 +204,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } } + if (!IsValidChannelNumber(numberString)) + { + if (attributes.TryGetValue("tvg-chno", out string value)) + { + numberString = value; + } + } + if (!string.IsNullOrWhiteSpace(numberString)) { numberString = numberString.Trim(); From edf5ee0cc43beaff4437ace4ae6b0202e414ec03 Mon Sep 17 00:00:00 2001 From: Felix Ruhnow Date: Mon, 11 Feb 2019 10:11:07 +0100 Subject: [PATCH 25/52] combining some of the if's and adressing #820 --- .../LiveTv/TunerHosts/M3uParser.cs | 88 +++++++++---------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index e10c445019..d2a835b850 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -157,64 +157,56 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var nameInExtInf = nameParts.Length > 1 ? nameParts.Last().Trim() : null; string numberString = null; + string attributeValue; + double doubleValue; - // Check for channel number with the format from SatIp - // #EXTINF:0,84. VOX Schweiz - // #EXTINF:0,84.0 - VOX Schweiz - if (!string.IsNullOrWhiteSpace(nameInExtInf)) + if (attributes.TryGetValue("tvg-chno", out attributeValue)) { - var numberIndex = nameInExtInf.IndexOf(' '); - if (numberIndex > 0) + if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue)) { - var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' }); + numberString = attributeValue; + } + } - if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number)) + if (!IsValidChannelNumber(numberString)) + { + if (attributes.TryGetValue("tvg-id", out attributeValue)) + { + if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue)) { - numberString = numberPart; + numberString = attributeValue; + } + else if (attributes.TryGetValue("channel-id", out attributeValue)) + { + if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue)) + { + numberString = attributeValue; + } } } - } - if (!string.IsNullOrWhiteSpace(numberString)) - { - numberString = numberString.Trim(); - } - - if (!IsValidChannelNumber(numberString)) - { - if (attributes.TryGetValue("tvg-id", out string value)) + if (String.IsNullOrWhiteSpace(numberString)) { - if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var doubleValue)) + // Using this as a fallback now as this leads to Problems with channels like "5 USA" + // where 5 isnt ment to be the channel number + // Check for channel number with the format from SatIp + // #EXTINF:0,84. VOX Schweiz + // #EXTINF:0,84.0 - VOX Schweiz + if (!string.IsNullOrWhiteSpace(nameInExtInf)) { - numberString = value; + var numberIndex = nameInExtInf.IndexOf(' '); + if (numberIndex > 0) + { + var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' }); + + if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number)) + { + numberString = numberPart; + } + } } } - } - if (!string.IsNullOrWhiteSpace(numberString)) - { - numberString = numberString.Trim(); - } - - if (!IsValidChannelNumber(numberString)) - { - if (attributes.TryGetValue("channel-id", out string value)) - { - numberString = value; - } - } - - if (!IsValidChannelNumber(numberString)) - { - if (attributes.TryGetValue("tvg-chno", out string value)) - { - numberString = value; - } - } - - if (!string.IsNullOrWhiteSpace(numberString)) - { - numberString = numberString.Trim(); } if (!IsValidChannelNumber(numberString)) @@ -222,7 +214,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts numberString = null; } - if (string.IsNullOrWhiteSpace(numberString)) + if (!string.IsNullOrWhiteSpace(numberString)) + { + numberString = numberString.Trim(); + } + else { if (string.IsNullOrWhiteSpace(mediaUrl)) { From 81a8ebde22e92c9d8f4292bfb18c9134f43bd956 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 3 Feb 2019 17:09:12 +0100 Subject: [PATCH 26/52] Move to Microsoft.Extensions.DependencyInjection This PR replaces SimpleInjector with Microsoft.Extensions.DependencyInjection. --- Emby.Dlna/DlnaManager.cs | 4 +- .../MediaReceiverRegistrar/ControlHandler.cs | 3 +- .../ApplicationHost.cs | 500 +++++------------- .../Emby.Server.Implementations.csproj | 3 +- Jellyfin.Server/Program.cs | 3 +- MediaBrowser.Common/IApplicationHost.cs | 16 +- .../MediaBrowser.Common.csproj | 4 + MediaBrowser.Model/System/PublicSystemInfo.cs | 6 +- MediaBrowser.Model/System/SystemInfo.cs | 3 +- 9 files changed, 152 insertions(+), 390 deletions(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index c507b14e9f..f53d274516 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -38,7 +38,9 @@ namespace Emby.Dlna IFileSystem fileSystem, IApplicationPaths appPaths, ILoggerFactory loggerFactory, - IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IAssemblyInfo assemblyInfo) + IJsonSerializer jsonSerializer, + IServerApplicationHost appHost, + IAssemblyInfo assemblyInfo) { _xmlSerializer = xmlSerializer; _fileSystem = fileSystem; diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs index 6257892b19..ae8175f4a2 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs @@ -36,7 +36,8 @@ namespace Emby.Dlna.MediaReceiverRegistrar }; } - public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(config, logger, xmlReaderSettingsFactory) + public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + : base(config, logger, xmlReaderSettingsFactory) { } } diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index bb475eb2c7..b9d38504c1 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -105,6 +105,7 @@ using MediaBrowser.Providers.Subtitles; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; using ServiceStack; using ServiceStack.Text.Jsv; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; @@ -202,7 +203,7 @@ namespace Emby.Server.Implementations /// Gets all concrete types. /// /// All concrete types. - public Tuple[] AllConcreteTypes { get; protected set; } + public Type[] AllConcreteTypes { get; protected set; } /// /// The disposable parts @@ -219,8 +220,6 @@ namespace Emby.Server.Implementations protected IEnvironmentInfo EnvironmentInfo { get; set; } - private IBlurayExaminer BlurayExaminer { get; set; } - public PackageVersionClass SystemUpdateLevel { get @@ -232,12 +231,7 @@ namespace Emby.Server.Implementations } } - public virtual string OperatingSystemDisplayName => EnvironmentInfo.OperatingSystemName; - - /// - /// The container - /// - protected readonly SimpleInjector.Container Container = new SimpleInjector.Container(); + protected IServiceProvider _serviceProvider; /// /// Gets the server configuration manager. @@ -453,138 +447,58 @@ namespace Emby.Server.Implementations /// The name. public string Name => ApplicationProductName; - private static Tuple GetAssembly(Type type) - { - var assembly = type.GetTypeInfo().Assembly; - - return new Tuple(assembly, null); - } - - public virtual IStreamHelper CreateStreamHelper() - { - return new StreamHelper(); - } - /// - /// Creates an instance of type and resolves all constructor dependancies + /// Creates an instance of type and resolves all constructor dependencies /// /// The type. /// System.Object. public object CreateInstance(Type type) - { - return Container.GetInstance(type); - } + => ActivatorUtilities.CreateInstance(_serviceProvider, type); + + /// + /// Creates an instance of type and resolves all constructor dependencies + /// + /// The type. + /// System.Object. + public T CreateInstance() + => ActivatorUtilities.CreateInstance(_serviceProvider); /// /// Creates the instance safe. /// /// The type information. /// System.Object. - protected object CreateInstanceSafe(Tuple typeInfo) + protected object CreateInstanceSafe(Type type) { - var type = typeInfo.Item1; - try { - return Container.GetInstance(type); + return ActivatorUtilities.CreateInstance(_serviceProvider, type); } catch (Exception ex) { - Logger.LogError(ex, "Error creating {type}", type.FullName); + Logger.LogError(ex, "Error creating {Type}", type); // Don't blow up in release mode return null; } } - /// - /// Registers the specified obj. - /// - /// - /// The obj. - /// if set to true [manage lifetime]. - protected void RegisterSingleInstance(T obj, bool manageLifetime = true) - where T : class - { - Container.RegisterInstance(obj); - - if (manageLifetime) - { - var disposable = obj as IDisposable; - - if (disposable != null) - { - DisposableParts.Add(disposable); - } - } - } - - /// - /// Registers the single instance. - /// - /// - /// The func. - protected void RegisterSingleInstance(Func func) - where T : class - { - Container.RegisterSingleton(func); - } - /// /// Resolves this instance. /// /// /// ``0. - public T Resolve() - { - return (T)Container.GetRegistration(typeof(T), true).GetInstance(); - } - - /// - /// Resolves this instance. - /// - /// - /// ``0. - public T TryResolve() - { - var result = Container.GetRegistration(typeof(T), false); - - if (result == null) - { - return default(T); - } - return (T)result.GetInstance(); - } - - /// - /// Loads the assembly. - /// - /// The file. - /// Assembly. - protected Tuple LoadAssembly(string file) - { - try - { - var assembly = Assembly.Load(File.ReadAllBytes(file)); - - return new Tuple(assembly, file); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error loading assembly {File}", file); - return null; - } - } + public T Resolve() => _serviceProvider.GetService(); /// /// Gets the export types. /// /// /// IEnumerable{Type}. - public IEnumerable> GetExportTypes() + public IEnumerable GetExportTypes() { var currentType = typeof(T); - return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i.Item1)); + return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i)); } /// @@ -596,7 +510,7 @@ namespace Emby.Server.Implementations public IEnumerable GetExports(bool manageLifetime = true) { var parts = GetExportTypes() - .Select(CreateInstanceSafe) + .Select(x => CreateInstanceSafe(x)) .Where(i => i != null) .Cast(); @@ -611,20 +525,16 @@ namespace Emby.Server.Implementations return parts; } - public List> GetExportsWithInfo(bool manageLifetime = true) + public List<(T, string)> GetExportsWithInfo(bool manageLifetime = true) { var parts = GetExportTypes() .Select(i => { var obj = CreateInstanceSafe(i); - if (obj == null) - { - return null; - } - return new Tuple((T)obj, i.Item2); + return ((T)obj, i.Assembly.Location); }) - .Where(i => i != null) + .Where(i => i.Item1 != null) .ToList(); if (manageLifetime) @@ -691,7 +601,7 @@ namespace Emby.Server.Implementations } } - public async Task Init() + public async Task Init(IServiceCollection serviceCollection) { HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; @@ -721,7 +631,7 @@ namespace Emby.Server.Implementations SetHttpLimit(); - await RegisterResources(); + await RegisterResources(serviceCollection); FindParts(); } @@ -736,104 +646,104 @@ namespace Emby.Server.Implementations /// /// Registers resources that classes will depend on /// - protected async Task RegisterResources() + protected async Task RegisterResources(IServiceCollection serviceCollection) { - RegisterSingleInstance(ConfigurationManager); - RegisterSingleInstance(this); + serviceCollection.AddSingleton(ConfigurationManager); + serviceCollection.AddSingleton(this); - RegisterSingleInstance(ApplicationPaths); + serviceCollection.AddSingleton(ApplicationPaths); - RegisterSingleInstance(JsonSerializer); - RegisterSingleInstance(LoggerFactory, false); - RegisterSingleInstance(Logger); + serviceCollection.AddSingleton(JsonSerializer); - RegisterSingleInstance(EnvironmentInfo); + serviceCollection.AddSingleton(LoggerFactory); + serviceCollection.AddLogging(); + serviceCollection.AddSingleton(Logger); - RegisterSingleInstance(FileSystemManager); + serviceCollection.AddSingleton(EnvironmentInfo); + + serviceCollection.AddSingleton(FileSystemManager); HttpClient = CreateHttpClient(); - RegisterSingleInstance(HttpClient); + serviceCollection.AddSingleton(HttpClient); - RegisterSingleInstance(NetworkManager); + serviceCollection.AddSingleton(NetworkManager); IsoManager = new IsoManager(); - RegisterSingleInstance(IsoManager); + serviceCollection.AddSingleton(IsoManager); TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager); - RegisterSingleInstance(TaskManager); + serviceCollection.AddSingleton(TaskManager); - RegisterSingleInstance(XmlSerializer); + serviceCollection.AddSingleton(XmlSerializer); ProcessFactory = new ProcessFactory(); - RegisterSingleInstance(ProcessFactory); + serviceCollection.AddSingleton(ProcessFactory); - var streamHelper = CreateStreamHelper(); - ApplicationHost.StreamHelper = streamHelper; - RegisterSingleInstance(streamHelper); + ApplicationHost.StreamHelper = new StreamHelper(); + serviceCollection.AddSingleton(StreamHelper); - RegisterSingleInstance(CryptographyProvider); + serviceCollection.AddSingleton(CryptographyProvider); SocketFactory = new SocketFactory(); - RegisterSingleInstance(SocketFactory); + serviceCollection.AddSingleton(SocketFactory); InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, PackageRuntime); - RegisterSingleInstance(InstallationManager); + serviceCollection.AddSingleton(InstallationManager); ZipClient = new ZipClient(FileSystemManager); - RegisterSingleInstance(ZipClient); + serviceCollection.AddSingleton(ZipClient); HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor()); - RegisterSingleInstance(HttpResultFactory); + serviceCollection.AddSingleton(HttpResultFactory); - RegisterSingleInstance(this); - RegisterSingleInstance(ApplicationPaths); + serviceCollection.AddSingleton(this); + serviceCollection.AddSingleton(ApplicationPaths); - RegisterSingleInstance(ServerConfigurationManager); + serviceCollection.AddSingleton(ServerConfigurationManager); - IAssemblyInfo assemblyInfo = new AssemblyInfo(); - RegisterSingleInstance(assemblyInfo); + var assemblyInfo = new AssemblyInfo(); + serviceCollection.AddSingleton(assemblyInfo); LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory); await LocalizationManager.LoadAll(); - RegisterSingleInstance(LocalizationManager); + serviceCollection.AddSingleton(LocalizationManager); - BlurayExaminer = new BdInfoExaminer(FileSystemManager); - RegisterSingleInstance(BlurayExaminer); + serviceCollection.AddSingleton(new BdInfoExaminer(FileSystemManager)); - RegisterSingleInstance(new XmlReaderSettingsFactory()); + serviceCollection.AddSingleton(new XmlReaderSettingsFactory()); UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager); - RegisterSingleInstance(UserDataManager); + serviceCollection.AddSingleton(UserDataManager); UserRepository = GetUserRepository(); // This is only needed for disposal purposes. If removing this, make sure to have the manager handle disposing it - RegisterSingleInstance(UserRepository); + serviceCollection.AddSingleton(UserRepository); var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LoggerFactory, JsonSerializer, ApplicationPaths, FileSystemManager); DisplayPreferencesRepository = displayPreferencesRepo; - RegisterSingleInstance(DisplayPreferencesRepository); + serviceCollection.AddSingleton(DisplayPreferencesRepository); ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo); - RegisterSingleInstance(ItemRepository); + serviceCollection.AddSingleton(ItemRepository); AuthenticationRepository = GetAuthenticationRepository(); - RegisterSingleInstance(AuthenticationRepository); + serviceCollection.AddSingleton(AuthenticationRepository); UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager, CryptographyProvider); - RegisterSingleInstance(UserManager); + serviceCollection.AddSingleton(UserManager); LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager); - RegisterSingleInstance(LibraryManager); + serviceCollection.AddSingleton(LibraryManager); // TODO wtaylor: investigate use of second music manager var musicManager = new MusicManager(LibraryManager); - RegisterSingleInstance(new MusicManager(LibraryManager)); + serviceCollection.AddSingleton(new MusicManager(LibraryManager)); LibraryMonitor = new LibraryMonitor(LoggerFactory, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo); - RegisterSingleInstance(LibraryMonitor); + serviceCollection.AddSingleton(LibraryMonitor); - RegisterSingleInstance(() => new SearchEngine(LoggerFactory, LibraryManager, UserManager)); + serviceCollection.AddSingleton(new SearchEngine(LoggerFactory, LibraryManager, UserManager)); CertificateInfo = GetCertificateInfo(true); Certificate = GetCertificate(CertificateInfo); @@ -848,81 +758,80 @@ namespace Emby.Server.Implementations GetParseFn); HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); - RegisterSingleInstance(HttpServer); + serviceCollection.AddSingleton(HttpServer); ImageProcessor = GetImageProcessor(); - RegisterSingleInstance(ImageProcessor); + serviceCollection.AddSingleton(ImageProcessor); TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager); - RegisterSingleInstance(TVSeriesManager); + serviceCollection.AddSingleton(TVSeriesManager); var encryptionManager = new EncryptionManager(); - RegisterSingleInstance(encryptionManager); + serviceCollection.AddSingleton(encryptionManager); DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LoggerFactory, NetworkManager); - RegisterSingleInstance(DeviceManager); + serviceCollection.AddSingleton(DeviceManager); MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder); - RegisterSingleInstance(MediaSourceManager); + serviceCollection.AddSingleton(MediaSourceManager); SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager); - RegisterSingleInstance(SubtitleManager); + serviceCollection.AddSingleton(SubtitleManager); ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, LoggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer); - RegisterSingleInstance(ProviderManager); + serviceCollection.AddSingleton(ProviderManager); DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, this, () => DeviceManager, () => MediaSourceManager, () => LiveTvManager); - RegisterSingleInstance(DtoService); + serviceCollection.AddSingleton(DtoService); ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager); - RegisterSingleInstance(ChannelManager); + serviceCollection.AddSingleton(ChannelManager); SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager); - RegisterSingleInstance(SessionManager); + serviceCollection.AddSingleton(SessionManager); - var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo); - RegisterSingleInstance(dlnaManager); + serviceCollection.AddSingleton(new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo)); CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager); - RegisterSingleInstance(CollectionManager); + serviceCollection.AddSingleton(CollectionManager); PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager); - RegisterSingleInstance(PlaylistManager); + serviceCollection.AddSingleton(PlaylistManager); LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager); - RegisterSingleInstance(LiveTvManager); + serviceCollection.AddSingleton(LiveTvManager); UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager); - RegisterSingleInstance(UserViewManager); + serviceCollection.AddSingleton(UserViewManager); NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager); - RegisterSingleInstance(NotificationManager); + serviceCollection.AddSingleton(NotificationManager); - RegisterSingleInstance(new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory)); + serviceCollection.AddSingleton(new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory)); ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); - RegisterSingleInstance(ChapterManager); + serviceCollection.AddSingleton(ChapterManager); - RegisterMediaEncoder(assemblyInfo); + RegisterMediaEncoder(serviceCollection); EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager); - RegisterSingleInstance(EncodingManager); + serviceCollection.AddSingleton(EncodingManager); var activityLogRepo = GetActivityLogRepository(); - RegisterSingleInstance(activityLogRepo); - RegisterSingleInstance(new ActivityManager(LoggerFactory, activityLogRepo, UserManager)); + serviceCollection.AddSingleton(activityLogRepo); + serviceCollection.AddSingleton(new ActivityManager(LoggerFactory, activityLogRepo, UserManager)); var authContext = new AuthorizationContext(AuthenticationRepository, UserManager); - RegisterSingleInstance(authContext); - RegisterSingleInstance(new SessionContext(UserManager, authContext, SessionManager)); + serviceCollection.AddSingleton(authContext); + serviceCollection.AddSingleton(new SessionContext(UserManager, authContext, SessionManager)); AuthService = new AuthService(UserManager, authContext, ServerConfigurationManager, SessionManager, NetworkManager); - RegisterSingleInstance(AuthService); + serviceCollection.AddSingleton(AuthService); SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory); - RegisterSingleInstance(SubtitleEncoder); + serviceCollection.AddSingleton(SubtitleEncoder); - RegisterSingleInstance(CreateResourceFileManager()); + serviceCollection.AddSingleton(CreateResourceFileManager()); displayPreferencesRepo.Initialize(); @@ -935,6 +844,8 @@ namespace Emby.Server.Implementations ((UserDataManager)UserDataManager).Repository = userDataRepo; ItemRepository.Initialize(userDataRepo, UserManager); ((LibraryManager)LibraryManager).ItemRepository = ItemRepository; + + _serviceProvider = serviceCollection.BuildServiceProvider(); } protected virtual IBrotliCompressor CreateBrotliCompressor() @@ -1066,7 +977,7 @@ namespace Emby.Server.Implementations /// Registers the media encoder. /// /// Task. - private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo) + private void RegisterMediaEncoder(IServiceCollection serviceCollection) { string encoderPath = null; string probePath = null; @@ -1098,7 +1009,7 @@ namespace Emby.Server.Implementations 5000); MediaEncoder = mediaEncoder; - RegisterSingleInstance(MediaEncoder); + serviceCollection.AddSingleton(MediaEncoder); } /// @@ -1208,7 +1119,7 @@ namespace Emby.Server.Implementations IsoManager.AddParts(GetExports()); } - private IPlugin LoadPlugin(Tuple info) + private IPlugin LoadPlugin((IPlugin, string) info) { var plugin = info.Item1; var assemblyFilePath = info.Item2; @@ -1264,78 +1175,15 @@ namespace Emby.Server.Implementations { Logger.LogInformation("Loading assemblies"); - var assemblyInfos = GetComposablePartAssemblies(); - - foreach (var assemblyInfo in assemblyInfos) - { - var assembly = assemblyInfo.Item1; - var path = assemblyInfo.Item2; - - if (path == null) + AllConcreteTypes = GetComposablePartAssemblies() + .SelectMany(x => x.ExportedTypes) + .Where(type => { - Logger.LogInformation("Loading {assemblyName}", assembly.FullName); - } - else - { - Logger.LogInformation("Loading {assemblyName} from {path}", assembly.FullName, path); - } - } - - AllConcreteTypes = assemblyInfos - .SelectMany(GetTypes) - .Where(info => - { - var t = info.Item1; - return t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType; + return type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType; }) .ToArray(); } - /// - /// Gets a list of types within an assembly - /// This will handle situations that would normally throw an exception - such as a type within the assembly that depends on some other non-existant reference - /// - protected List> GetTypes(Tuple assemblyInfo) - { - if (assemblyInfo == null) - { - return new List>(); - } - - var assembly = assemblyInfo.Item1; - - try - { - // This null checking really shouldn't be needed but adding it due to some - // unhandled exceptions in mono 5.0 that are a little hard to hunt down - var types = assembly.GetTypes() ?? new Type[] { }; - return types.Where(t => t != null).Select(i => new Tuple(i, assemblyInfo.Item2)).ToList(); - } - catch (ReflectionTypeLoadException ex) - { - if (ex.LoaderExceptions != null) - { - foreach (var loaderException in ex.LoaderExceptions) - { - if (loaderException != null) - { - Logger.LogError("LoaderException: " + loaderException.Message); - } - } - } - - // If it fails we can still get a list of the Types it was able to resolve - var types = ex.Types ?? new Type[] { }; - return types.Where(t => t != null).Select(i => new Tuple(i, assemblyInfo.Item2)).ToList(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error loading types from assembly"); - - return new List>(); - } - } - private CertificateInfo CertificateInfo { get; set; } protected X509Certificate Certificate { get; private set; } @@ -1546,151 +1394,67 @@ namespace Emby.Server.Implementations /// Gets the composable part assemblies. /// /// IEnumerable{Assembly}. - protected List> GetComposablePartAssemblies() + protected IEnumerable GetComposablePartAssemblies() { - var list = GetPluginAssemblies(ApplicationPaths.PluginsPath); + if (Directory.Exists(ApplicationPaths.PluginsPath)) + { + foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly)) + { + Logger.LogInformation("Loading assembly {Path}", file); + yield return Assembly.LoadFile(file); + } + } // Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that // This will prevent the .dll file from getting locked, and allow us to replace it when needed // Include composable parts in the Api assembly - list.Add(GetAssembly(typeof(ApiEntryPoint))); + yield return typeof(ApiEntryPoint).Assembly; // Include composable parts in the Dashboard assembly - list.Add(GetAssembly(typeof(DashboardService))); + yield return typeof(DashboardService).Assembly; // Include composable parts in the Model assembly - list.Add(GetAssembly(typeof(SystemInfo))); + yield return typeof(SystemInfo).Assembly; // Include composable parts in the Common assembly - list.Add(GetAssembly(typeof(IApplicationHost))); + yield return typeof(IApplicationHost).Assembly; // Include composable parts in the Controller assembly - list.Add(GetAssembly(typeof(IServerApplicationHost))); + yield return typeof(IServerApplicationHost).Assembly; // Include composable parts in the Providers assembly - list.Add(GetAssembly(typeof(ProviderUtils))); + yield return typeof(ProviderUtils).Assembly; // Include composable parts in the Photos assembly - list.Add(GetAssembly(typeof(PhotoProvider))); + yield return typeof(PhotoProvider).Assembly; // Emby.Server implementations - list.Add(GetAssembly(typeof(InstallationManager))); + yield return typeof(InstallationManager).Assembly; // MediaEncoding - list.Add(GetAssembly(typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder))); + yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly; // Dlna - list.Add(GetAssembly(typeof(DlnaEntryPoint))); + yield return typeof(DlnaEntryPoint).Assembly; // Local metadata - list.Add(GetAssembly(typeof(BoxSetXmlSaver))); + yield return typeof(BoxSetXmlSaver).Assembly; // Notifications - list.Add(GetAssembly(typeof(NotificationManager))); + yield return typeof(NotificationManager).Assembly; // Xbmc - list.Add(GetAssembly(typeof(ArtistNfoProvider))); + yield return typeof(ArtistNfoProvider).Assembly; - list.AddRange(GetAssembliesWithPartsInternal().Select(i => new Tuple(i, null))); - - return list.ToList(); + foreach (var i in GetAssembliesWithPartsInternal()) + { + yield return i; + } } protected abstract IEnumerable GetAssembliesWithPartsInternal(); - private List> GetPluginAssemblies(string path) - { - try - { - return FilterAssembliesToLoad(Directory.EnumerateFiles(path, "*.dll", SearchOption.TopDirectoryOnly)) - .Select(LoadAssembly) - .Where(a => a != null) - .ToList(); - } - catch (DirectoryNotFoundException) - { - return new List>(); - } - } - - private IEnumerable FilterAssembliesToLoad(IEnumerable paths) - { - - var exclude = new[] - { - "mbplus.dll", - "mbintros.dll", - "embytv.dll", - "Messenger.dll", - "Messages.dll", - "MediaBrowser.Plugins.TvMazeProvider.dll", - "MBBookshelf.dll", - "MediaBrowser.Channels.Adult.YouJizz.dll", - "MediaBrowser.Channels.Vine-co.dll", - "MediaBrowser.Plugins.Vimeo.dll", - "MediaBrowser.Channels.Vevo.dll", - "MediaBrowser.Plugins.Twitch.dll", - "MediaBrowser.Channels.SvtPlay.dll", - "MediaBrowser.Plugins.SoundCloud.dll", - "MediaBrowser.Plugins.SnesBox.dll", - "MediaBrowser.Plugins.RottenTomatoes.dll", - "MediaBrowser.Plugins.Revision3.dll", - "MediaBrowser.Plugins.NesBox.dll", - "MBChapters.dll", - "MediaBrowser.Channels.LeagueOfLegends.dll", - "MediaBrowser.Plugins.ADEProvider.dll", - "MediaBrowser.Channels.BallStreams.dll", - "MediaBrowser.Channels.Adult.Beeg.dll", - "ChannelDownloader.dll", - "Hamstercat.Emby.EmbyBands.dll", - "EmbyTV.dll", - "MediaBrowser.Channels.HitboxTV.dll", - "MediaBrowser.Channels.HockeyStreams.dll", - "MediaBrowser.Plugins.ITV.dll", - "MediaBrowser.Plugins.Lastfm.dll", - "ServerRestart.dll", - "MediaBrowser.Plugins.NotifyMyAndroidNotifications.dll", - "MetadataViewer.dll" - }; - - var minRequiredVersions = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "moviethemesongs.dll", new Version(1, 6) }, - { "themesongs.dll", new Version(1, 2) } - }; - - return paths.Where(path => - { - var filename = Path.GetFileName(path); - if (exclude.Contains(filename ?? string.Empty, StringComparer.OrdinalIgnoreCase)) - { - return false; - } - - if (minRequiredVersions.TryGetValue(filename, out Version minRequiredVersion)) - { - try - { - var version = Version.Parse(FileVersionInfo.GetVersionInfo(path).FileVersion); - - if (version < minRequiredVersion) - { - Logger.LogInformation("Not loading {filename} {version} because the minimum supported version is {minRequiredVersion}. Please update to the newer version", filename, version, minRequiredVersion); - return false; - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error getting version number from {path}", path); - - return false; - } - } - return true; - }); - } - /// /// Gets the system status. /// @@ -1718,7 +1482,7 @@ namespace Emby.Server.Implementations SupportsHttps = SupportsHttps, HttpsPortNumber = HttpsPort, OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(), - OperatingSystemDisplayName = OperatingSystemDisplayName, + OperatingSystemDisplayName = EnvironmentInfo.OperatingSystemName, CanSelfRestart = CanSelfRestart, CanSelfUpdate = CanSelfUpdate, CanLaunchWebBrowser = CanLaunchWebBrowser, @@ -1788,7 +1552,7 @@ namespace Emby.Server.Implementations public async Task GetWanApiUrl(CancellationToken cancellationToken) { - var url = "http://ipv4.icanhazip.com"; + const string url = "http://ipv4.icanhazip.com"; try { using (var response = await HttpClient.Get(new HttpRequestOptions diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 8356a9501b..6bf776f533 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -22,9 +22,10 @@ + + - diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 7826fde35b..2e6226b228 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -21,6 +21,7 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; using Serilog.AspNetCore; @@ -114,7 +115,7 @@ namespace Jellyfin.Server new NullImageEncoder(), new NetworkManager(_loggerFactory, environmentInfo))) { - await appHost.Init(); + await appHost.Init(new ServiceCollection()); appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager); diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 59e3c17674..6891152ee2 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Events; using MediaBrowser.Model.Updates; +using Microsoft.Extensions.DependencyInjection; namespace MediaBrowser.Common { @@ -13,12 +14,6 @@ namespace MediaBrowser.Common /// public interface IApplicationHost { - /// - /// Gets the display name of the operating system. - /// - /// The display name of the operating system. - string OperatingSystemDisplayName { get; } - /// /// Gets the name. /// @@ -104,13 +99,6 @@ namespace MediaBrowser.Common /// ``0. T Resolve(); - /// - /// Resolves this instance. - /// - /// - /// ``0. - T TryResolve(); - /// /// Shuts down. /// @@ -131,7 +119,7 @@ namespace MediaBrowser.Common /// /// Inits this instance. /// - Task Init(); + Task Init(IServiceCollection serviceCollection); /// /// Creates the instance. diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 2220d46610..715f4fccd3 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -11,6 +11,10 @@ + + + + diff --git a/MediaBrowser.Model/System/PublicSystemInfo.cs b/MediaBrowser.Model/System/PublicSystemInfo.cs index accdc9e601..d97eda3523 100644 --- a/MediaBrowser.Model/System/PublicSystemInfo.cs +++ b/MediaBrowser.Model/System/PublicSystemInfo.cs @@ -24,12 +24,12 @@ namespace MediaBrowser.Model.System /// Gets or sets the server version. /// /// The version. - public string Version { get; set; } + public string Version { get; set; } /// - /// Gets or sets the operating sytem. + /// Gets or sets the operating system. /// - /// The operating sytem. + /// The operating system. public string OperatingSystem { get; set; } /// diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index 26f7353307..d9ed68b27e 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -1,3 +1,4 @@ +using System; using System.Runtime.InteropServices; using MediaBrowser.Model.Updates; @@ -136,7 +137,7 @@ namespace MediaBrowser.Model.System /// public SystemInfo() { - CompletedInstallations = new InstallationInfo[] { }; + CompletedInstallations = Array.Empty(); } } } From 9af28607c990f5ddcd738c72d7183b9a57ba47c2 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 6 Feb 2019 14:04:32 +0100 Subject: [PATCH 27/52] Simplify plugin loading --- .../ApplicationHost.cs | 45 +++++-------------- Jellyfin.Server/CoreAppHost.cs | 4 +- 2 files changed, 13 insertions(+), 36 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index b9d38504c1..5dcf42aae9 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -303,7 +303,6 @@ namespace Emby.Server.Implementations /// The user data repository. private IUserDataManager UserDataManager { get; set; } private IUserRepository UserRepository { get; set; } - internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; } internal SqliteItemRepository ItemRepository { get; set; } private INotificationManager NotificationManager { get; set; } @@ -472,6 +471,7 @@ namespace Emby.Server.Implementations { try { + Logger.LogWarning("Creating instance of {Type}", type); return ActivatorUtilities.CreateInstance(_serviceProvider, type); } catch (Exception ex) @@ -525,29 +525,6 @@ namespace Emby.Server.Implementations return parts; } - public List<(T, string)> GetExportsWithInfo(bool manageLifetime = true) - { - var parts = GetExportTypes() - .Select(i => - { - var obj = CreateInstanceSafe(i); - - return ((T)obj, i.Assembly.Location); - }) - .Where(i => i.Item1 != null) - .ToList(); - - if (manageLifetime) - { - lock (DisposableParts) - { - DisposableParts.AddRange(parts.Select(i => i.Item1).OfType()); - } - } - - return parts; - } - /// /// Runs the startup tasks. /// @@ -721,8 +698,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(UserRepository); var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LoggerFactory, JsonSerializer, ApplicationPaths, FileSystemManager); - DisplayPreferencesRepository = displayPreferencesRepo; - serviceCollection.AddSingleton(DisplayPreferencesRepository); + serviceCollection.AddSingleton(displayPreferencesRepo); ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo); serviceCollection.AddSingleton(ItemRepository); @@ -1085,7 +1061,10 @@ namespace Emby.Server.Implementations } ConfigurationManager.AddParts(GetExports()); - Plugins = GetExportsWithInfo().Select(LoadPlugin).Where(i => i != null).ToArray(); + Plugins = GetExports() + .Select(LoadPlugin) + .Where(i => i != null) + .ToArray(); HttpServer.Init(GetExports(false), GetExports()); @@ -1119,19 +1098,15 @@ namespace Emby.Server.Implementations IsoManager.AddParts(GetExports()); } - private IPlugin LoadPlugin((IPlugin, string) info) + private IPlugin LoadPlugin(IPlugin plugin) { - var plugin = info.Item1; - var assemblyFilePath = info.Item2; - try { - var assemblyPlugin = plugin as IPluginAssembly; - - if (assemblyPlugin != null) + if (plugin is IPluginAssembly assemblyPlugin) { var assembly = plugin.GetType().Assembly; var assemblyName = assembly.GetName(); + var assemblyFilePath = assembly.Location; var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); @@ -1401,7 +1376,7 @@ namespace Emby.Server.Implementations foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly)) { Logger.LogInformation("Loading assembly {Path}", file); - yield return Assembly.LoadFile(file); + yield return Assembly.LoadFrom(file); } } diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 315e34a04c..a486c2a475 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -21,7 +21,9 @@ namespace Jellyfin.Server protected override void RestartInternal() => Program.Restart(); protected override IEnumerable GetAssembliesWithPartsInternal() - => new[] { typeof(CoreAppHost).Assembly }; + { + yield return typeof(CoreAppHost).Assembly; + } protected override void ShutdownInternal() => Program.Shutdown(); From d409623086e210d9256ce0863f6fb8dad57e69ef Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 6 Feb 2019 17:41:38 +0100 Subject: [PATCH 28/52] Don't create multiple instances of the same type --- Emby.Server.Implementations/ApplicationHost.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 5dcf42aae9..41ac4fcffa 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -512,7 +512,8 @@ namespace Emby.Server.Implementations var parts = GetExportTypes() .Select(x => CreateInstanceSafe(x)) .Where(i => i != null) - .Cast(); + .Cast() + .ToList(); // Convert to list so this isn't executed for each iteration if (manageLifetime) { From c74028d08d4fcbd2a921423f5b2b6dea7012a11e Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 12 Feb 2019 20:50:47 +0100 Subject: [PATCH 29/52] Address comments --- Emby.Server.Implementations/ApplicationHost.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 41ac4fcffa..784486d524 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -477,7 +477,6 @@ namespace Emby.Server.Implementations catch (Exception ex) { Logger.LogError(ex, "Error creating {Type}", type); - // Don't blow up in release mode return null; } } @@ -767,7 +766,8 @@ namespace Emby.Server.Implementations SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager); serviceCollection.AddSingleton(SessionManager); - serviceCollection.AddSingleton(new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo)); + serviceCollection.AddSingleton( + new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo)); CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager); serviceCollection.AddSingleton(CollectionManager); @@ -784,7 +784,8 @@ namespace Emby.Server.Implementations NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager); serviceCollection.AddSingleton(NotificationManager); - serviceCollection.AddSingleton(new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory)); + serviceCollection.AddSingleton( + new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory)); ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); serviceCollection.AddSingleton(ChapterManager); @@ -1381,9 +1382,6 @@ namespace Emby.Server.Implementations } } - // Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that - // This will prevent the .dll file from getting locked, and allow us to replace it when needed - // Include composable parts in the Api assembly yield return typeof(ApiEntryPoint).Assembly; From a5882ae162e1bdae7f9094c6bb63cf765ad8123f Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 12 Feb 2019 22:15:11 +0100 Subject: [PATCH 30/52] Treat warnings as errors for release builds --- Jellyfin.Server/Jellyfin.Server.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index b1515df439..fe1397bcbe 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -20,6 +20,10 @@ + + true + + From a2dd2ddd55adc001bf3752dce6d4a95616e5dc4b Mon Sep 17 00:00:00 2001 From: ploughpuff <33969763+ploughpuff@users.noreply.github.com> Date: Wed, 13 Feb 2019 15:35:14 +0000 Subject: [PATCH 31/52] Rewrite rules for determining app paths and use XDG_CONFIG_HOME for configDir (#781) Re-write rules for determining dataDir, configDir and logDir. Generally, arguments from command line take precedence, then JELLYFIN env vars, before using XDG names. Co-Authored-By: ploughpuff <33969763+ploughpuff@users.noreply.github.com> --- .../AppBase/BaseApplicationPaths.cs | 103 +++-------- .../ServerApplicationPaths.cs | 15 +- Jellyfin.Server/Program.cs | 168 +++++++++++------- 3 files changed, 132 insertions(+), 154 deletions(-) diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index 701c04f9e2..f26cc4f62e 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using MediaBrowser.Common.Configuration; @@ -14,50 +15,44 @@ namespace Emby.Server.Implementations.AppBase /// protected BaseApplicationPaths( string programDataPath, - string appFolderPath, - string logDirectoryPath = null, - string configurationDirectoryPath = null, - string cacheDirectoryPath = null) + string logDirectoryPath, + string configurationDirectoryPath, + string cacheDirectoryPath) { ProgramDataPath = programDataPath; - ProgramSystemPath = appFolderPath; LogDirectoryPath = logDirectoryPath; ConfigurationDirectoryPath = configurationDirectoryPath; CachePath = cacheDirectoryPath; + + DataPath = Path.Combine(ProgramDataPath, "data"); } + /// + /// Gets the path to the program data folder + /// + /// The program data path. public string ProgramDataPath { get; private set; } /// /// Gets the path to the system folder /// - public string ProgramSystemPath { get; private set; } + public string ProgramSystemPath { get; } = AppContext.BaseDirectory; - /// - /// The _data directory - /// - private string _dataDirectory; /// /// Gets the folder path to the data directory /// /// The data directory. + private string _dataPath; public string DataPath { - get - { - if (_dataDirectory == null) - { - _dataDirectory = Path.Combine(ProgramDataPath, "data"); - - Directory.CreateDirectory(_dataDirectory); - } - - return _dataDirectory; - } + get => _dataPath; + private set => _dataPath = Directory.CreateDirectory(value).FullName; } - private const string _virtualDataPath = "%AppDataPath%"; - public string VirtualDataPath => _virtualDataPath; + /// + /// Gets the magic strings used for virtual path manipulation. + /// + public string VirtualDataPath { get; } = "%AppDataPath%"; /// /// Gets the image cache path. @@ -83,55 +78,17 @@ namespace Emby.Server.Implementations.AppBase /// The plugin configurations path. public string TempUpdatePath => Path.Combine(ProgramDataPath, "updates"); - /// - /// The _log directory - /// - private string _logDirectoryPath; - /// /// Gets the path to the log directory /// /// The log directory path. - public string LogDirectoryPath - { - get - { - if (string.IsNullOrEmpty(_logDirectoryPath)) - { - _logDirectoryPath = Path.Combine(ProgramDataPath, "logs"); - - Directory.CreateDirectory(_logDirectoryPath); - } - - return _logDirectoryPath; - } - set => _logDirectoryPath = value; - } - - /// - /// The _config directory - /// - private string _configurationDirectoryPath; + public string LogDirectoryPath { get; private set; } /// /// Gets the path to the application configuration root directory /// /// The configuration directory path. - public string ConfigurationDirectoryPath - { - get - { - if (string.IsNullOrEmpty(_configurationDirectoryPath)) - { - _configurationDirectoryPath = Path.Combine(ProgramDataPath, "config"); - - Directory.CreateDirectory(_configurationDirectoryPath); - } - - return _configurationDirectoryPath; - } - set => _configurationDirectoryPath = value; - } + public string ConfigurationDirectoryPath { get; private set; } /// /// Gets the path to the system configuration file @@ -139,29 +96,11 @@ namespace Emby.Server.Implementations.AppBase /// The system configuration file path. public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml"); - /// - /// The _cache directory - /// - private string _cachePath; /// /// Gets the folder path to the cache directory /// /// The cache directory. - public string CachePath - { - get - { - if (string.IsNullOrEmpty(_cachePath)) - { - _cachePath = Path.Combine(ProgramDataPath, "cache"); - - Directory.CreateDirectory(_cachePath); - } - - return _cachePath; - } - set => _cachePath = value; - } + public string CachePath { get; set; } /// /// Gets the folder path to the temp directory within the cache folder diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index 36975df50c..05f6469ece 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -15,21 +15,17 @@ namespace Emby.Server.Implementations /// public ServerApplicationPaths( string programDataPath, - string appFolderPath, - string applicationResourcesPath, - string logDirectoryPath = null, - string configurationDirectoryPath = null, - string cacheDirectoryPath = null) + string logDirectoryPath, + string configurationDirectoryPath, + string cacheDirectoryPath) : base(programDataPath, - appFolderPath, logDirectoryPath, configurationDirectoryPath, cacheDirectoryPath) { - ApplicationResourcesPath = applicationResourcesPath; } - public string ApplicationResourcesPath { get; private set; } + public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory; /// /// Gets the path to the base root media directory @@ -148,7 +144,6 @@ namespace Emby.Server.Implementations set => _internalMetadataPath = value; } - private const string _virtualInternalMetadataPath = "%MetadataPath%"; - public string VirtualInternalMetadataPath => _virtualInternalMetadataPath; + public string VirtualInternalMetadataPath { get; } = "%MetadataPath%"; } } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 7826fde35b..6d372c36f4 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -139,112 +139,156 @@ namespace Jellyfin.Server } } + /// + /// Create the data, config and log paths from the variety of inputs(command line args, + /// environment variables) or decide on what default to use. For Windows it's %AppPath% + /// for everything else the XDG approach is followed: + /// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + /// + /// + /// ServerApplicationPaths private static ServerApplicationPaths CreateApplicationPaths(StartupOptions options) { - string programDataPath = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH"); - if (string.IsNullOrEmpty(programDataPath)) + // dataDir + // IF --datadir + // ELSE IF $JELLYFIN_DATA_PATH + // ELSE IF windows, use <%APPDATA%>/jellyfin + // ELSE IF $XDG_DATA_HOME then use $XDG_DATA_HOME/jellyfin + // ELSE use $HOME/.local/share/jellyfin + var dataDir = options.DataDir; + + if (string.IsNullOrEmpty(dataDir)) { - if (options.DataDir != null) - { - programDataPath = options.DataDir; - } - else + dataDir = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH"); + + if (string.IsNullOrEmpty(dataDir)) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + dataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); } else { // $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored. - programDataPath = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); - // If $XDG_DATA_HOME is either not set or empty, $HOME/.local/share should be used. - if (string.IsNullOrEmpty(programDataPath)) + dataDir = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); + + // If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used. + if (string.IsNullOrEmpty(dataDir)) { - programDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share"); + dataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share"); } } - programDataPath = Path.Combine(programDataPath, "jellyfin"); + dataDir = Path.Combine(dataDir, "jellyfin"); } } - if (string.IsNullOrEmpty(programDataPath)) - { - Console.WriteLine("Cannot continue without path to program data folder (try -programdata)"); - Environment.Exit(1); - } - else - { - Directory.CreateDirectory(programDataPath); - } + // configDir + // IF --configdir + // ELSE IF $JELLYFIN_CONFIG_DIR + // ELSE IF --datadir, use /config (assume portable run) + // ELSE IF /config exists, use that + // ELSE IF windows, use /config + // ELSE IF $XDG_CONFIG_HOME use $XDG_CONFIG_HOME/jellyfin + // ELSE $HOME/.config/jellyfin + var configDir = options.ConfigDir; - string configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR"); if (string.IsNullOrEmpty(configDir)) { - if (options.ConfigDir != null) + configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR"); + + if (string.IsNullOrEmpty(configDir)) { - configDir = options.ConfigDir; - } - else - { - // Let BaseApplicationPaths set up the default value - configDir = null; + if (options.DataDir != null || Directory.Exists(Path.Combine(dataDir, "config")) || RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Hang config folder off already set dataDir + configDir = Path.Combine(dataDir, "config"); + } + else + { + // $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should be stored. + configDir = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME"); + + // If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME /.config should be used. + if (string.IsNullOrEmpty(configDir)) + { + configDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config"); + } + + configDir = Path.Combine(configDir, "jellyfin"); + } } } - if (configDir != null) - { - Directory.CreateDirectory(configDir); - } + // cacheDir + // IF --cachedir + // ELSE IF $JELLYFIN_CACHE_DIR + // ELSE IF windows, use /cache + // ELSE IF XDG_CACHE_HOME, use $XDG_CACHE_HOME/jellyfin + // ELSE HOME/.cache/jellyfin + var cacheDir = options.CacheDir; - string cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR"); if (string.IsNullOrEmpty(cacheDir)) { - if (options.CacheDir != null) + cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR"); + + if (string.IsNullOrEmpty(cacheDir)) { - cacheDir = options.CacheDir; - } - else if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored. - cacheDir = Environment.GetEnvironmentVariable("XDG_CACHE_HOME"); - // If $XDG_CACHE_HOME is either not set or empty, $HOME/.cache should be used. - if (string.IsNullOrEmpty(cacheDir)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache"); + // Hang cache folder off already set dataDir + cacheDir = Path.Combine(dataDir, "cache"); + } + else + { + // $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored. + cacheDir = Environment.GetEnvironmentVariable("XDG_CACHE_HOME"); + + // If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache should be used. + if (string.IsNullOrEmpty(cacheDir)) + { + cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache"); + } + + cacheDir = Path.Combine(cacheDir, "jellyfin"); } - cacheDir = Path.Combine(cacheDir, "jellyfin"); } } - if (cacheDir != null) - { - Directory.CreateDirectory(cacheDir); - } + // logDir + // IF --logdir + // ELSE IF $JELLYFIN_LOG_DIR + // ELSE IF --datadir, use /log (assume portable run) + // ELSE /log + var logDir = options.LogDir; - string logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR"); if (string.IsNullOrEmpty(logDir)) { - if (options.LogDir != null) + logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR"); + + if (string.IsNullOrEmpty(logDir)) { - logDir = options.LogDir; - } - else - { - // Let BaseApplicationPaths set up the default value - logDir = null; + // Hang log folder off already set dataDir + logDir = Path.Combine(dataDir, "log"); } } - if (logDir != null) + // Ensure the main folders exist before we continue + try { + Directory.CreateDirectory(dataDir); Directory.CreateDirectory(logDir); + Directory.CreateDirectory(configDir); + Directory.CreateDirectory(cacheDir); + } + catch (IOException ex) + { + Console.Error.WriteLine("Error whilst attempting to create folder"); + Console.Error.WriteLine(ex.ToString()); + Environment.Exit(1); } - string appPath = AppContext.BaseDirectory; - - return new ServerApplicationPaths(programDataPath, appPath, appPath, logDir, configDir, cacheDir); + return new ServerApplicationPaths(dataDir, logDir, configDir, cacheDir); } private static async Task CreateLogger(IApplicationPaths appPaths) From 5d4bef5478a55700f9e8b3dda5615881c81fd8cf Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 14 Feb 2019 16:40:43 +0300 Subject: [PATCH 32/52] Update jellyfin-web submodule to master as of 14.02.2019 --- MediaBrowser.WebDashboard/jellyfin-web | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.WebDashboard/jellyfin-web b/MediaBrowser.WebDashboard/jellyfin-web index 094c1deae9..c7ce1ac8ec 160000 --- a/MediaBrowser.WebDashboard/jellyfin-web +++ b/MediaBrowser.WebDashboard/jellyfin-web @@ -1 +1 @@ -Subproject commit 094c1deae91c51b8bbf8ebb16a55758af110f04d +Subproject commit c7ce1ac8eccd50f1bd759b30fbe60ea797ffe86e From dbebc4774f37231f308f55a76373735442f98ff9 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Thu, 14 Feb 2019 20:06:20 +0100 Subject: [PATCH 33/52] Switched to Roslyn naming rules --- .editorconfig | 80 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 9 deletions(-) diff --git a/.editorconfig b/.editorconfig index b2891188d8..141fd87a56 100644 --- a/.editorconfig +++ b/.editorconfig @@ -55,15 +55,77 @@ dotnet_style_prefer_conditional_expression_over_return = true:silent ############################### # Naming Conventions # ############################### -# Style Definitions -dotnet_naming_style.pascal_case_style.capitalization = pascal_case -# Use PascalCase for constant fields -dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields -dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style -dotnet_naming_symbols.constant_fields.applicable_kinds = field -dotnet_naming_symbols.constant_fields.applicable_accessibilities = * -dotnet_naming_symbols.constant_fields.required_modifiers = const +# Style Definitions (From Roslyn) + +# Non-private static fields are PascalCase +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style + +dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field +dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected +dotnet_naming_symbols.non_private_static_fields.required_modifiers = static + +dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case + +# Constants are PascalCase +dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants +dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style + +dotnet_naming_symbols.constants.applicable_kinds = field, local +dotnet_naming_symbols.constants.required_modifiers = const + +dotnet_naming_style.constant_style.capitalization = pascal_case + +# Static fields are camelCase and start with s_ +dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields +dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style + +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static + +dotnet_naming_style.static_field_style.capitalization = camel_case +dotnet_naming_style.static_field_style.required_prefix = s_ + +# Instance fields are camelCase and start with _ +dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields +dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style + +dotnet_naming_symbols.instance_fields.applicable_kinds = field + +dotnet_naming_style.instance_field_style.capitalization = camel_case +dotnet_naming_style.instance_field_style.required_prefix = _ + +# Locals and parameters are camelCase +dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion +dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters +dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style + +dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local + +dotnet_naming_style.camel_case_style.capitalization = camel_case + +# Local functions are PascalCase +dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions +dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style + +dotnet_naming_symbols.local_functions.applicable_kinds = local_function + +dotnet_naming_style.local_function_style.capitalization = pascal_case + +# By default, name items with PascalCase +dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members +dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style + +dotnet_naming_symbols.all_members.applicable_kinds = * + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + ############################### # C# Coding Conventions # ############################### From db1ebe25591e0933731c7d7eb710127ee6aa6a6b Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Thu, 14 Feb 2019 20:14:29 +0100 Subject: [PATCH 34/52] Static fields prefix 's_' -> '_' --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 141fd87a56..21f27df131 100644 --- a/.editorconfig +++ b/.editorconfig @@ -87,7 +87,7 @@ dotnet_naming_symbols.static_fields.applicable_kinds = field dotnet_naming_symbols.static_fields.required_modifiers = static dotnet_naming_style.static_field_style.capitalization = camel_case -dotnet_naming_style.static_field_style.required_prefix = s_ +dotnet_naming_style.static_field_style.required_prefix = _ # Instance fields are camelCase and start with _ dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion From 21f0a7e020dab7b667559b8adc51a35147941d41 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Fri, 15 Feb 2019 23:05:14 +0100 Subject: [PATCH 35/52] Make all class implementing dynamically loaded interfaces public --- .../Channels/RefreshChannelsScheduledTask.cs | 2 +- .../EntryPoints/UserDataChangeNotifier.cs | 2 +- .../Library/Resolvers/SpecialFolderResolver.cs | 2 +- .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 2 +- .../Sorting/AiredEpisodeOrderComparer.cs | 2 +- Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs | 2 +- MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs | 2 +- MediaBrowser.Api/System/ActivityLogWebSocketListener.cs | 2 +- MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs | 2 +- MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs | 2 +- MediaBrowser.Providers/Movies/MovieDbImageProvider.cs | 2 +- MediaBrowser.Providers/Music/MusicVideoMetadataService.cs | 2 +- MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs | 2 +- MediaBrowser.Providers/Photos/PhotoMetadataService.cs | 2 +- MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs | 2 +- MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs | 2 +- MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs | 2 +- MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs index 844f77a1ab..303a8ac7b1 100644 --- a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Channels { - class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask + public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask { private readonly IChannelManager _channelManager; private readonly IUserManager _userManager; diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 774ed09dab..a5badaceec 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints { - class UserDataChangeNotifier : IServerEntryPoint + public class UserDataChangeNotifier : IServerEntryPoint { private readonly ISessionManager _sessionManager; private readonly ILogger _logger; diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs index fa8c89e88d..7e4b38b4c0 100644 --- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs @@ -9,7 +9,7 @@ using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.Library.Resolvers { - class SpecialFolderResolver : FolderResolver + public class SpecialFolderResolver : FolderResolver { private readonly IFileSystem _fileSystem; private readonly IServerApplicationPaths _appPaths; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 81fdb96d2f..2f07ff15a9 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// Class ChapterImagesTask /// - class ChapterImagesTask : IScheduledTask + public class ChapterImagesTask : IScheduledTask { /// /// The _logger diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 271188314c..16507466f9 100644 --- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { - class AiredEpisodeOrderComparer : IBaseItemComparer + public class AiredEpisodeOrderComparer : IBaseItemComparer { /// /// Compares the specified x. diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs index 942e847041..46e0dd9186 100644 --- a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -5,7 +5,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { - class SeriesSortNameComparer : IBaseItemComparer + public class SeriesSortNameComparer : IBaseItemComparer { /// /// Compares the specified x. diff --git a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs index 387ccad25e..beb2fb11d0 100644 --- a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs +++ b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Api.Session /// /// Class SessionInfoWebSocketListener /// - class SessionInfoWebSocketListener : BasePeriodicWebSocketListener, WebSocketListenerState> + public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener, WebSocketListenerState> { /// /// Gets the name. diff --git a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs index 0df46c3996..43f3c5a223 100644 --- a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs +++ b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Api.System /// /// Class SessionInfoWebSocketListener /// - class ActivityLogWebSocketListener : BasePeriodicWebSocketListener, WebSocketListenerState> + public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener, WebSocketListenerState> { /// /// Gets the name. diff --git a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs index 4180a4f153..442a18cb98 100644 --- a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Providers { - class PlaylistXmlProvider : BaseXmlProvider + public class PlaylistXmlProvider : BaseXmlProvider { private readonly ILogger _logger; private readonly IProviderManager _providerManager; diff --git a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs index c6c1a2a945..4d12b2f4ab 100644 --- a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs +++ b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs @@ -14,7 +14,7 @@ using MediaBrowser.Providers.Movies; namespace MediaBrowser.Providers.BoxSets { - class MovieDbBoxSetImageProvider : IRemoteImageProvider, IHasOrder + public class MovieDbBoxSetImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClient _httpClient; diff --git a/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs index b9c5d7ce59..20b53d58ad 100644 --- a/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs @@ -16,7 +16,7 @@ using MediaBrowser.Model.Serialization; namespace MediaBrowser.Providers.Movies { - class MovieDbImageProvider : IRemoteImageProvider, IHasOrder + public class MovieDbImageProvider : IRemoteImageProvider, IHasOrder { private readonly IJsonSerializer _jsonSerializer; private readonly IHttpClient _httpClient; diff --git a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs index 8b01ff3427..93412306fc 100644 --- a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs +++ b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Music { - class MusicVideoMetadataService : MetadataService + public class MusicVideoMetadataService : MetadataService { protected override void MergeData(MetadataResult source, MetadataResult target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { diff --git a/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs b/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs index fd969c7c29..993581cca8 100644 --- a/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs +++ b/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Photos { - class PhotoAlbumMetadataService : MetadataService + public class PhotoAlbumMetadataService : MetadataService { protected override void MergeData(MetadataResult source, MetadataResult target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { diff --git a/MediaBrowser.Providers/Photos/PhotoMetadataService.cs b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs index a430e1041d..b739c57654 100644 --- a/MediaBrowser.Providers/Photos/PhotoMetadataService.cs +++ b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Photos { - class PhotoMetadataService : MetadataService + public class PhotoMetadataService : MetadataService { protected override void MergeData(MetadataResult source, MetadataResult target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { diff --git a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs index b28d2a5489..30ce5c64ca 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs @@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Playlists { - class PlaylistMetadataService : MetadataService + public class PlaylistMetadataService : MetadataService { protected override IList GetChildrenForMetadataUpdates(Playlist item) { diff --git a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs index d0749405b4..dee3030af1 100644 --- a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs @@ -16,7 +16,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.TV.Omdb { - class OmdbEpisodeProvider : + public class OmdbEpisodeProvider : IRemoteMetadataProvider, IHasOrder { diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs index 44590515ee..3d77450859 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs @@ -20,7 +20,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.TV.TheMovieDb { - class MovieDbEpisodeProvider : + public class MovieDbEpisodeProvider : MovieDbProviderBase, IRemoteMetadataProvider, IHasOrder diff --git a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs index 21ee8f92f4..a3e48d30d9 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs @@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Parsers { - class MovieNfoParser : BaseNfoParser /// The receive action. public Action OnReceiveBytes { get; set; } - - /// - /// Gets or sets the on receive. - /// - /// The on receive. - public Action OnReceive { get; set; } } } diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs index a0ffd1ccf4..4b6649217b 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs @@ -201,7 +201,7 @@ namespace Jellyfin.Server.SocketSharp } catch (ObjectDisposedException) { - //TODO Investigate and properly fix. + // TODO: Investigate and properly fix. } catch (Exception ex) { @@ -252,7 +252,7 @@ namespace Jellyfin.Server.SocketSharp Stop(); } - //release unmanaged resources here... + // release unmanaged resources here... _disposed = true; } } diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs index ebeb18ea06..37fc6fe12d 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Server.SocketSharp this.request = httpContext.Request; this.response = new WebSocketSharpResponse(logger, httpContext.Response, this); - //HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); + // HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); } private static string GetHandlerPathIfAny(string listenerUrl) diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs index cabc96b237..68995a819e 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs @@ -51,7 +51,7 @@ namespace Jellyfin.Server.SocketSharp set => _response.ContentType = value; } - //public ICookies Cookies { get; set; } + // public ICookies Cookies { get; set; } public void AddHeader(string name, string value) { @@ -114,9 +114,9 @@ namespace Jellyfin.Server.SocketSharp public void SetContentLength(long contentLength) { - //you can happily set the Content-Length header in Asp.Net - //but HttpListener will complain if you do - you have to set ContentLength64 on the response. - //workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header + // you can happily set the Content-Length header in Asp.Net + // but HttpListener will complain if you do - you have to set ContentLength64 on the response. + // workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header _response.ContentLength64 = contentLength; } @@ -147,10 +147,6 @@ namespace Jellyfin.Server.SocketSharp { sb.Append($";domain={cookie.Domain}"); } - //else if (restrictAllCookiesToDomain != null) - //{ - // sb.Append($";domain={restrictAllCookiesToDomain}"); - //} if (cookie.Secure) { From d8b312674db11b59c90747c5d2510ed25ec40b06 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Feb 2019 16:54:37 +0100 Subject: [PATCH 39/52] No multiple empty lines --- Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs index 68995a819e..a4e6aac351 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs @@ -13,7 +13,6 @@ using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse; using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IRequest = MediaBrowser.Model.Services.IRequest; - namespace Jellyfin.Server.SocketSharp { public class WebSocketSharpResponse : IHttpResponse @@ -160,7 +159,6 @@ namespace Jellyfin.Server.SocketSharp return sb.ToString(); } - public bool SendChunked { get => _response.SendChunked; From fc59b0ab77b60cb9d06a5027ee0f2363e39fbea7 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Feb 2019 16:57:55 +0100 Subject: [PATCH 40/52] Disable SA1512 --- jellyfin.ruleset | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 0f8c9aa020..295a45dc94 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -7,6 +7,8 @@ + + From 637936cb9f2734df8e7c4c5838430bf5069901e6 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Feb 2019 16:59:30 +0100 Subject: [PATCH 41/52] Closing braces should be followed by an empty line --- Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs | 3 +++ Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs | 1 + 2 files changed, 4 insertions(+) diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs index 37fc6fe12d..a0f9e6d2d2 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs @@ -140,10 +140,12 @@ namespace Jellyfin.Server.SocketSharp throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); } } + if (crlf != 0) { throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); } + return name; } @@ -156,6 +158,7 @@ namespace Jellyfin.Server.SocketSharp return true; } } + return false; } diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs index a4e6aac351..6de678b86c 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs @@ -151,6 +151,7 @@ namespace Jellyfin.Server.SocketSharp { sb.Append(";Secure"); } + if (cookie.HttpOnly) { sb.Append(";HttpOnly"); From 183ef34422656468a9e145f37ed4be7a70823194 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Feb 2019 17:03:50 +0100 Subject: [PATCH 42/52] Do not declare visible instance fields --- Jellyfin.Server/SocketSharp/RequestMono.cs | 8 ++++++-- Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/SocketSharp/RequestMono.cs b/Jellyfin.Server/SocketSharp/RequestMono.cs index af8fe96167..4bb2e8e190 100644 --- a/Jellyfin.Server/SocketSharp/RequestMono.cs +++ b/Jellyfin.Server/SocketSharp/RequestMono.cs @@ -127,8 +127,12 @@ namespace Jellyfin.Server.SocketSharp public string Authorization => string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"]; - protected bool validate_cookies, validate_query_string, validate_form; - protected bool checked_cookies, checked_query_string, checked_form; + protected bool validate_cookies { get; set; } + protected bool validate_query_string { get; set; } + protected bool validate_form { get; set; } + protected bool checked_cookies { get; set; } + protected bool checked_query_string { get; set; } + protected bool checked_form { get; set; } private static void ThrowValidationException(string name, string key, string value) { diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs index a0f9e6d2d2..c5fdf6a3b0 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs @@ -447,7 +447,7 @@ namespace Jellyfin.Server.SocketSharp public string ContentType => request.ContentType; - public Encoding contentEncoding; + private Encoding contentEncoding; public Encoding ContentEncoding { get => contentEncoding ?? request.ContentEncoding; From 34af7501fa33f807aa7e721a95294b623f2d4d6c Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Feb 2019 17:06:32 +0100 Subject: [PATCH 43/52] Fix up CoreAppHost.cs --- Jellyfin.Server/CoreAppHost.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index a486c2a475..126cb467f9 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -18,6 +18,8 @@ namespace Jellyfin.Server public override bool CanSelfRestart => StartupOptions.RestartPath != null; + protected override bool SupportsDualModeSockets => true; + protected override void RestartInternal() => Program.Restart(); protected override IEnumerable GetAssembliesWithPartsInternal() @@ -27,8 +29,6 @@ namespace Jellyfin.Server protected override void ShutdownInternal() => Program.Shutdown(); - protected override bool SupportsDualModeSockets => true; - protected override IHttpListener CreateHttpListener() => new WebSocketSharpListener( Logger, @@ -39,7 +39,6 @@ namespace Jellyfin.Server CryptographyProvider, SupportsDualModeSockets, FileSystemManager, - EnvironmentInfo - ); + EnvironmentInfo); } } From be77e14db96b709fc0dc212258c3389a210e1af4 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Feb 2019 17:19:55 +0100 Subject: [PATCH 44/52] Warnings for docs --- Jellyfin.Server/Jellyfin.Server.csproj | 3 ++ Jellyfin.Server/Program.cs | 40 ++++++++++--------- Jellyfin.Server/SocketSharp/RequestMono.cs | 2 - Jellyfin.Server/SocketSharp/SharpWebSocket.cs | 1 + 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index fe1397bcbe..a6bf23f5be 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -5,11 +5,14 @@ Exe netcoreapp2.1 false + true latest + + SA1600 diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 6dfabc3644..ac5aab4609 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -57,6 +57,21 @@ namespace Jellyfin.Server errs => Task.FromResult(0)).ConfigureAwait(false); } + public static void Shutdown() + { + if (!_tokenSource.IsCancellationRequested) + { + _tokenSource.Cancel(); + } + } + + public static void Restart() + { + _restartOnShutdown = true; + + Shutdown(); + } + private static async Task StartApp(StartupOptions options) { ServerApplicationPaths appPaths = CreateApplicationPaths(options); @@ -76,6 +91,7 @@ namespace Jellyfin.Server { return; // Already shutting down } + e.Cancel = true; _logger.LogInformation("Ctrl+C, shutting down"); Environment.ExitCode = 128 + 2; @@ -89,6 +105,7 @@ namespace Jellyfin.Server { return; // Already shutting down } + _logger.LogInformation("Received a SIGTERM signal, shutting down"); Environment.ExitCode = 128 + 15; Shutdown(); @@ -102,7 +119,7 @@ namespace Jellyfin.Server SQLitePCL.Batteries_V2.Init(); // Allow all https requests - ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); + ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; } ); var fileSystem = new ManagedFileSystem(_loggerFactory, environmentInfo, null, appPaths.TempDirectory, true); @@ -146,7 +163,7 @@ namespace Jellyfin.Server /// for everything else the XDG approach is followed: /// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html /// - /// + /// StartupOptions /// ServerApplicationPaths private static ServerApplicationPaths CreateApplicationPaths(StartupOptions options) { @@ -308,6 +325,7 @@ namespace Jellyfin.Server await rscstr.CopyToAsync(fstr).ConfigureAwait(false); } } + var configuration = new ConfigurationBuilder() .SetBasePath(appPaths.ConfigurationDirectoryPath) .AddJsonFile("logging.json") @@ -335,7 +353,7 @@ namespace Jellyfin.Server } } - public static IImageEncoder GetImageEncoder( + private static IImageEncoder GetImageEncoder( IFileSystem fileSystem, IApplicationPaths appPaths, ILocalizationManager localizationManager) @@ -376,26 +394,12 @@ namespace Jellyfin.Server { return MediaBrowser.Model.System.OperatingSystem.BSD; } + throw new Exception($"Can't resolve OS with description: '{osDescription}'"); } } } - public static void Shutdown() - { - if (!_tokenSource.IsCancellationRequested) - { - _tokenSource.Cancel(); - } - } - - public static void Restart() - { - _restartOnShutdown = true; - - Shutdown(); - } - private static void StartNewInstance(StartupOptions options) { _logger.LogInformation("Starting new instance"); diff --git a/Jellyfin.Server/SocketSharp/RequestMono.cs b/Jellyfin.Server/SocketSharp/RequestMono.cs index 4bb2e8e190..584d38bcbc 100644 --- a/Jellyfin.Server/SocketSharp/RequestMono.cs +++ b/Jellyfin.Server/SocketSharp/RequestMono.cs @@ -82,9 +82,7 @@ namespace Jellyfin.Server.SocketSharp } else { - // // We use a substream, as in 2.x we will support large uploads streamed to disk, - // var sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length); files[e.Name] = sub; } diff --git a/Jellyfin.Server/SocketSharp/SharpWebSocket.cs b/Jellyfin.Server/SocketSharp/SharpWebSocket.cs index e7e36e31be..5b233cdf5d 100644 --- a/Jellyfin.Server/SocketSharp/SharpWebSocket.cs +++ b/Jellyfin.Server/SocketSharp/SharpWebSocket.cs @@ -55,6 +55,7 @@ namespace Jellyfin.Server.SocketSharp void socket_OnError(object sender, SocketHttpListener.ErrorEventArgs e) { _logger.LogError("Error in SharpWebSocket: {Message}", e.Message ?? string.Empty); + // Closed?.Invoke(this, EventArgs.Empty); } From 892787cb1a72303ffd514971d0d5ea4c30f2dcd1 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Feb 2019 17:25:47 +0100 Subject: [PATCH 45/52] Disable SA1130 --- jellyfin.ruleset | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 295a45dc94..25d11d7d35 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -3,6 +3,8 @@ + + From 46897aab4f85bd2ea50e0ff1ef57c0cfa8f48c67 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Feb 2019 17:28:04 +0100 Subject: [PATCH 46/52] More warnings --- Jellyfin.Server/SocketSharp/RequestMono.cs | 9 ++------- Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs | 7 +++++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Jellyfin.Server/SocketSharp/RequestMono.cs b/Jellyfin.Server/SocketSharp/RequestMono.cs index 584d38bcbc..9a06017501 100644 --- a/Jellyfin.Server/SocketSharp/RequestMono.cs +++ b/Jellyfin.Server/SocketSharp/RequestMono.cs @@ -431,13 +431,13 @@ namespace Jellyfin.Server.SocketSharp real = position + d; break; default: - throw new ArgumentException(nameof(origin)); + throw new ArgumentException("Unknown SeekOrigin value", nameof(origin)); } long virt = real - offset; if (virt < 0 || virt > Length) { - throw new ArgumentException(); + throw new ArgumentException("Invalid position", nameof(origin)); } position = s.Seek(real, SeekOrigin.Begin); @@ -572,11 +572,6 @@ namespace Jellyfin.Server.SocketSharp public HttpMultipart(Stream data, string b, Encoding encoding) { this.data = data; - // DB: 30/01/11: cannot set or read the Position in HttpListener in Win.NET - // var ms = new MemoryStream(32 * 1024); - // data.CopyTo(ms); - // this.data = ms; - boundary = b; boundary_bytes = encoding.GetBytes(b); buffer = new byte[boundary_bytes.Length + 2]; // CRLF or '--' diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs index c5fdf6a3b0..a19ea6bf7a 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Text; using Emby.Server.Implementations.HttpServer; @@ -69,9 +70,11 @@ namespace Jellyfin.Server.SocketSharp public string UserHostAddress => request.UserHostAddress; - public string XForwardedFor => string.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"]; + public string XForwardedFor + => string.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"]; - public int? XForwardedPort => string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"]); + public int? XForwardedPort + => string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"], CultureInfo.InvariantCulture); public string XForwardedProtocol => string.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"]; From 2cb747651b134313ffdca930af38b07002a38992 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Feb 2019 17:36:10 +0100 Subject: [PATCH 47/52] Correctly dispose WebSocketSharpListener --- .../SocketSharp/WebSocketSharpListener.cs | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs index 4b6649217b..90bd981f5b 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs @@ -223,38 +223,39 @@ namespace Jellyfin.Server.SocketSharp public Task Stop() { _disposeCancellationTokenSource.Cancel(); - - if (_listener != null) - { - _listener.Close(); - } + _listener?.Close(); return Task.CompletedTask; } + /// + /// Releases the unmanaged resources and disposes of the managed resources used. + /// public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } private bool _disposed; - private readonly object _disposeLock = new object(); + + /// + /// Releases the unmanaged resources and disposes of the managed resources used. + /// + /// Whether or not the managed resources should be disposed protected virtual void Dispose(bool disposing) { - if (_disposed) return; - - lock (_disposeLock) + if (_disposed) { - if (_disposed) return; - - if (disposing) - { - Stop(); - } - - // release unmanaged resources here... - _disposed = true; + return; } + + if (disposing) + { + Stop().GetAwaiter().GetResult(); + } + + _disposed = true; } } } From 8b04fe76332e70ab579f0f9ac84012ef310e73cf Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Feb 2019 18:37:44 +0100 Subject: [PATCH 48/52] More fixes --- Jellyfin.Server/SocketSharp/RequestMono.cs | 37 ++++++++++++------- Jellyfin.Server/SocketSharp/SharpWebSocket.cs | 27 +++++++++----- .../SocketSharp/WebSocketSharpListener.cs | 22 ++++++++--- .../SocketSharp/WebSocketSharpRequest.cs | 13 ++++++- 4 files changed, 70 insertions(+), 29 deletions(-) diff --git a/Jellyfin.Server/SocketSharp/RequestMono.cs b/Jellyfin.Server/SocketSharp/RequestMono.cs index 9a06017501..dd38b058b1 100644 --- a/Jellyfin.Server/SocketSharp/RequestMono.cs +++ b/Jellyfin.Server/SocketSharp/RequestMono.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Server.SocketSharp { internal static string GetParameter(string header, string attr) { - int ap = header.IndexOf(attr); + int ap = header.IndexOf(attr, StringComparison.Ordinal); if (ap == -1) { return null; @@ -140,8 +140,12 @@ namespace Jellyfin.Server.SocketSharp v = v.Substring(0, 16) + "...\""; } - string msg = string.Format("A potentially dangerous Request.{0} value was " + - "detected from the client ({1}={2}).", name, key, v); + string msg = string.Format( + CultureInfo.InvariantCulture, + "A potentially dangerous Request.{0} value was detected from the client ({1}={2}).", + name, + key, + v); throw new Exception(msg); } @@ -258,6 +262,7 @@ namespace Jellyfin.Server.SocketSharp value.Append((char)c); } } + if (c == -1) { AddRawKeyValue(form, key, value); @@ -273,6 +278,7 @@ namespace Jellyfin.Server.SocketSharp key.Append((char)c); } } + if (c == -1) { AddRawKeyValue(form, key, value); @@ -310,6 +316,7 @@ namespace Jellyfin.Server.SocketSharp result.Append(key); result.Append('='); } + result.Append(pair.Value); } @@ -493,11 +500,6 @@ namespace Jellyfin.Server.SocketSharp public Stream InputStream => stream; } - private class Helpers - { - public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture; - } - internal static class StrUtils { public static bool StartsWith(string str1, string str2, bool ignore_case) @@ -535,12 +537,17 @@ namespace Jellyfin.Server.SocketSharp public class Element { - public string ContentType; - public string Name; - public string Filename; - public Encoding Encoding; - public long Start; - public long Length; + public string ContentType { get; set; } + + public string Name { get; set; } + + public string Filename { get; set; } + + public Encoding Encoding { get; set; } + + public long Start { get; set; } + + public long Length { get; set; } public override string ToString() { @@ -597,6 +604,7 @@ namespace Jellyfin.Server.SocketSharp { break; } + got_cr = b == CR; sb.Append((char)b); } @@ -797,6 +805,7 @@ namespace Jellyfin.Server.SocketSharp c = data.ReadByte(); continue; } + data.Position = retval + 2; if (got_cr) { diff --git a/Jellyfin.Server/SocketSharp/SharpWebSocket.cs b/Jellyfin.Server/SocketSharp/SharpWebSocket.cs index 5b233cdf5d..6eee4cd12e 100644 --- a/Jellyfin.Server/SocketSharp/SharpWebSocket.cs +++ b/Jellyfin.Server/SocketSharp/SharpWebSocket.cs @@ -24,6 +24,7 @@ namespace Jellyfin.Server.SocketSharp private TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private bool _disposed = false; public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger) { @@ -40,9 +41,9 @@ namespace Jellyfin.Server.SocketSharp _logger = logger; WebSocket = socket; - socket.OnMessage += socket_OnMessage; - socket.OnClose += socket_OnClose; - socket.OnError += socket_OnError; + socket.OnMessage += OnSocketMessage; + socket.OnClose += OnSocketClose; + socket.OnError += OnSocketError; WebSocket.ConnectAsServer(); } @@ -52,21 +53,21 @@ namespace Jellyfin.Server.SocketSharp return _taskCompletionSource.Task; } - void socket_OnError(object sender, SocketHttpListener.ErrorEventArgs e) + private void OnSocketError(object sender, SocketHttpListener.ErrorEventArgs e) { _logger.LogError("Error in SharpWebSocket: {Message}", e.Message ?? string.Empty); // Closed?.Invoke(this, EventArgs.Empty); } - void socket_OnClose(object sender, SocketHttpListener.CloseEventArgs e) + private void OnSocketClose(object sender, SocketHttpListener.CloseEventArgs e) { _taskCompletionSource.TrySetResult(true); Closed?.Invoke(this, EventArgs.Empty); } - void socket_OnMessage(object sender, SocketHttpListener.MessageEventArgs e) + private void OnSocketMessage(object sender, SocketHttpListener.MessageEventArgs e) { if (OnReceiveBytes != null) { @@ -110,6 +111,7 @@ namespace Jellyfin.Server.SocketSharp public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// @@ -118,16 +120,23 @@ namespace Jellyfin.Server.SocketSharp /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { + if (_disposed) + { + return; + } + if (dispose) { - WebSocket.OnMessage -= socket_OnMessage; - WebSocket.OnClose -= socket_OnClose; - WebSocket.OnError -= socket_OnError; + WebSocket.OnMessage -= OnSocketMessage; + WebSocket.OnClose -= OnSocketClose; + WebSocket.OnError -= OnSocketError; _cancellationTokenSource.Cancel(); WebSocket.Close(); } + + _disposed = true; } /// diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs index 90bd981f5b..58c4d38a24 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs @@ -34,9 +34,16 @@ namespace Jellyfin.Server.SocketSharp private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); private CancellationToken _disposeCancellationToken; - public WebSocketSharpListener(ILogger logger, X509Certificate certificate, IStreamHelper streamHelper, - INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, - bool enableDualMode, IFileSystem fileSystem, IEnvironmentInfo environment) + public WebSocketSharpListener( + ILogger logger, + X509Certificate certificate, + IStreamHelper streamHelper, + INetworkManager networkManager, + ISocketFactory socketFactory, + ICryptoProvider cryptoProvider, + bool enableDualMode, + IFileSystem fileSystem, + IEnvironmentInfo environment) { _logger = logger; _certificate = certificate; @@ -61,7 +68,9 @@ namespace Jellyfin.Server.SocketSharp public void Start(IEnumerable urlPrefixes) { if (_listener == null) + { _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _networkManager, _streamHelper, _fileSystem, _environment); + } _listener.EnableDualMode = _enableDualMode; @@ -90,8 +99,11 @@ namespace Jellyfin.Server.SocketSharp { var url = request.Url.ToString(); - logger.LogInformation("{0} {1}. UserAgent: {2}", - request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty); + logger.LogInformation( + "{0} {1}. UserAgent: {2}", + request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, + url, + request.UserAgent ?? string.Empty); } private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken) diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs index a19ea6bf7a..cbce7d4571 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs @@ -42,7 +42,7 @@ namespace Jellyfin.Server.SocketSharp } var startHostUrl = listenerUrl.Substring(pos + "://".Length); - var endPos = startHostUrl.IndexOf('/'); + var endPos = startHostUrl.IndexOf('/', StringComparison.Ordinal); if (endPos == -1) { return null; @@ -110,6 +110,7 @@ namespace Jellyfin.Server.SocketSharp switch (crlf) { case 0: + { if (c == '\r') { crlf = 1; @@ -124,23 +125,31 @@ namespace Jellyfin.Server.SocketSharp { throw new ArgumentException("net_WebHeaderInvalidControlChars"); } + break; + } case 1: + { if (c == '\n') { crlf = 2; break; } + throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + } case 2: + { if (c == ' ' || c == '\t') { crlf = 0; break; } + throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + } } } @@ -349,6 +358,7 @@ namespace Jellyfin.Server.SocketSharp this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo); this.pathInfo = NormalizePathInfo(pathInfo, mode); } + return this.pathInfo; } } @@ -508,6 +518,7 @@ namespace Jellyfin.Server.SocketSharp i++; } } + return httpFiles; } } From e620bb95123f76c6eea92226a1e4c10d094ea65a Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Feb 2019 18:47:26 +0100 Subject: [PATCH 49/52] Remove more doc warnings --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- jellyfin.ruleset | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index a6bf23f5be..1f2287a758 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -12,7 +12,7 @@ latest - SA1600 + SA1600;CS1591 diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 25d11d7d35..4381349ca6 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -14,4 +14,8 @@ + + + + From cb9e50b2eae1cff7f21e893dee309a7ff629bd31 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Feb 2019 18:55:23 +0100 Subject: [PATCH 50/52] Reorder elements --- Jellyfin.Server/SocketSharp/RequestMono.cs | 105 ++++++++++-------- .../SocketSharp/WebSocketSharpResponse.cs | 7 +- 2 files changed, 61 insertions(+), 51 deletions(-) diff --git a/Jellyfin.Server/SocketSharp/RequestMono.cs b/Jellyfin.Server/SocketSharp/RequestMono.cs index dd38b058b1..fe8e8242a6 100644 --- a/Jellyfin.Server/SocketSharp/RequestMono.cs +++ b/Jellyfin.Server/SocketSharp/RequestMono.cs @@ -185,6 +185,7 @@ namespace Jellyfin.Server.SocketSharp for (int idx = 1; idx < len; idx++) { char next = val[idx]; + // See http://secunia.com/advisories/14325 if (current == '<' || current == '\xff1c') { @@ -556,15 +557,23 @@ namespace Jellyfin.Server.SocketSharp } } - private Stream data; - private string boundary; - private byte[] boundary_bytes; - private byte[] buffer; - private bool at_eof; - private Encoding encoding; - private StringBuilder sb; + private const byte LF = (byte)'\n'; - private const byte LF = (byte)'\n', CR = (byte)'\r'; + private const byte CR = (byte)'\r'; + + private Stream data; + + private string boundary; + + private byte[] boundaryBytes; + + private byte[] buffer; + + private bool atEof; + + private Encoding encoding; + + private StringBuilder sb; // See RFC 2046 // In the case of multipart entities, in which one or more different @@ -580,12 +589,48 @@ namespace Jellyfin.Server.SocketSharp { this.data = data; boundary = b; - boundary_bytes = encoding.GetBytes(b); - buffer = new byte[boundary_bytes.Length + 2]; // CRLF or '--' + boundaryBytes = encoding.GetBytes(b); + buffer = new byte[boundaryBytes.Length + 2]; // CRLF or '--' this.encoding = encoding; sb = new StringBuilder(); } + public Element ReadNextElement() + { + if (atEof || ReadBoundary()) + { + return null; + } + + var elem = new Element(); + string header; + while ((header = ReadHeaders()) != null) + { + if (StrUtils.StartsWith(header, "Content-Disposition:", true)) + { + elem.Name = GetContentDispositionAttribute(header, "name"); + elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename")); + } + else if (StrUtils.StartsWith(header, "Content-Type:", true)) + { + elem.ContentType = header.Substring("Content-Type:".Length).Trim(); + elem.Encoding = GetEncoding(elem.ContentType); + } + } + + long start = 0; + start = data.Position; + elem.Start = start; + long pos = MoveToNextBoundary(); + if (pos == -1) + { + return null; + } + + elem.Length = pos - start; + return elem; + } + private string ReadLine() { // CRLF or LF are ok as line endings. @@ -774,7 +819,7 @@ namespace Jellyfin.Server.SocketSharp return -1; } - if (!CompareBytes(boundary_bytes, buffer)) + if (!CompareBytes(boundaryBytes, buffer)) { state = 0; data.Position = retval + 2; @@ -790,7 +835,7 @@ namespace Jellyfin.Server.SocketSharp if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-') { - at_eof = true; + atEof = true; } else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF) { @@ -824,42 +869,6 @@ namespace Jellyfin.Server.SocketSharp return retval; } - public Element ReadNextElement() - { - if (at_eof || ReadBoundary()) - { - return null; - } - - var elem = new Element(); - string header; - while ((header = ReadHeaders()) != null) - { - if (StrUtils.StartsWith(header, "Content-Disposition:", true)) - { - elem.Name = GetContentDispositionAttribute(header, "name"); - elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename")); - } - else if (StrUtils.StartsWith(header, "Content-Type:", true)) - { - elem.ContentType = header.Substring("Content-Type:".Length).Trim(); - elem.Encoding = GetEncoding(elem.ContentType); - } - } - - long start = 0; - start = data.Position; - elem.Start = start; - long pos = MoveToNextBoundary(); - if (pos == -1) - { - return null; - } - - elem.Length = pos - start; - return elem; - } - private static string StripPath(string path) { if (path == null || path.Length == 0) diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs index 6de678b86c..56e5c73d61 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs @@ -18,6 +18,7 @@ namespace Jellyfin.Server.SocketSharp public class WebSocketSharpResponse : IHttpResponse { private readonly ILogger _logger; + private readonly HttpListenerResponse _response; public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request) @@ -29,7 +30,9 @@ namespace Jellyfin.Server.SocketSharp } public IRequest Request { get; private set; } + public Dictionary Items { get; private set; } + public object OriginalResponse => _response; public int StatusCode @@ -50,7 +53,7 @@ namespace Jellyfin.Server.SocketSharp set => _response.ContentType = value; } - // public ICookies Cookies { get; set; } + public QueryParamCollection Headers => _response.Headers; public void AddHeader(string name, string value) { @@ -63,8 +66,6 @@ namespace Jellyfin.Server.SocketSharp _response.AddHeader(name, value); } - public QueryParamCollection Headers => _response.Headers; - public string GetHeader(string name) { return _response.Headers[name]; From 3947f2315dea8b7e390e219f188d0ecf3f5ee2d2 Mon Sep 17 00:00:00 2001 From: Vasily Date: Sat, 16 Feb 2019 00:05:47 +0100 Subject: [PATCH 51/52] Update Jellyfin.Server/Jellyfin.Server.csproj Co-Authored-By: Bond-009 --- 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 1f2287a758..bd670df527 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -11,7 +11,7 @@ latest - + SA1600;CS1591 From 18e1d03a89b7fe3ddba925f81b153d0f0cd1973c Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Sat, 16 Feb 2019 00:42:09 +0100 Subject: [PATCH 52/52] Comments --- Jellyfin.Server/SocketSharp/RequestMono.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/SocketSharp/RequestMono.cs b/Jellyfin.Server/SocketSharp/RequestMono.cs index fe8e8242a6..24cf994eaf 100644 --- a/Jellyfin.Server/SocketSharp/RequestMono.cs +++ b/Jellyfin.Server/SocketSharp/RequestMono.cs @@ -445,7 +445,7 @@ namespace Jellyfin.Server.SocketSharp long virt = real - offset; if (virt < 0 || virt > Length) { - throw new ArgumentException("Invalid position", nameof(origin)); + throw new ArgumentException("Invalid position", nameof(d)); } position = s.Seek(real, SeekOrigin.Begin); @@ -618,8 +618,7 @@ namespace Jellyfin.Server.SocketSharp } } - long start = 0; - start = data.Position; + long start = data.Position; elem.Start = start; long pos = MoveToNextBoundary(); if (pos == -1)