mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
Merge branch 'master' into IsRoot_fix
This commit is contained in:
commit
51fb6e1d2d
@ -1,59 +0,0 @@
|
|||||||
parameters:
|
|
||||||
- name: LinuxImage
|
|
||||||
type: string
|
|
||||||
default: "ubuntu-latest"
|
|
||||||
- name: GeneratorVersion
|
|
||||||
type: string
|
|
||||||
default: "5.0.1"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
- job: GenerateApiClients
|
|
||||||
displayName: 'Generate Api Clients'
|
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
|
||||||
dependsOn: Test
|
|
||||||
|
|
||||||
pool:
|
|
||||||
vmImage: "${{ parameters.LinuxImage }}"
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
displayName: 'Download OpenAPI Spec Artifact'
|
|
||||||
inputs:
|
|
||||||
source: 'current'
|
|
||||||
artifact: "OpenAPI Spec"
|
|
||||||
path: "$(System.ArtifactsDirectory)/openapispec"
|
|
||||||
runVersion: "latest"
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: 'Download OpenApi Generator'
|
|
||||||
inputs:
|
|
||||||
script: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar"
|
|
||||||
|
|
||||||
## Authenticate with npm registry
|
|
||||||
- task: npmAuthenticate@0
|
|
||||||
inputs:
|
|
||||||
workingFile: ./.npmrc
|
|
||||||
customEndpoint: 'jellyfin-bot for NPM'
|
|
||||||
|
|
||||||
## Generate npm api client
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: 'Build stable typescript axios client'
|
|
||||||
inputs:
|
|
||||||
script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory)"
|
|
||||||
|
|
||||||
## Run npm install
|
|
||||||
- task: Npm@1
|
|
||||||
displayName: 'Install npm dependencies'
|
|
||||||
inputs:
|
|
||||||
command: install
|
|
||||||
workingDir: ./apiclient/generated/typescript/axios
|
|
||||||
|
|
||||||
## Publish npm packages
|
|
||||||
- task: Npm@1
|
|
||||||
displayName: 'Publish stable typescript axios client'
|
|
||||||
inputs:
|
|
||||||
command: custom
|
|
||||||
customCommand: publish --access public
|
|
||||||
publishRegistry: useExternalRegistry
|
|
||||||
publishEndpoint: 'jellyfin-bot for NPM'
|
|
||||||
workingDir: ./apiclient/generated/typescript/axios
|
|
@ -61,6 +61,3 @@ jobs:
|
|||||||
|
|
||||||
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
|
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
|
||||||
- template: azure-pipelines-package.yml
|
- template: azure-pipelines-package.yml
|
||||||
|
|
||||||
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
|
|
||||||
- template: azure-pipelines-api-client.yml
|
|
||||||
|
43
.github/label-commenter-config.yml
vendored
Normal file
43
.github/label-commenter-config.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
comment:
|
||||||
|
header: Hello @{{ issue.user.login }}
|
||||||
|
footer: "\
|
||||||
|
---\n\n
|
||||||
|
> This is an automated comment created by the [peaceiris/actions-label-commenter]. \
|
||||||
|
Responding to the bot or mentioning it won't have any effect.\n\n
|
||||||
|
[peaceiris/actions-label-commenter]: https://github.com/peaceiris/actions-label-commenter
|
||||||
|
"
|
||||||
|
|
||||||
|
labels:
|
||||||
|
- name: stable backport
|
||||||
|
labeled:
|
||||||
|
pr:
|
||||||
|
body: |
|
||||||
|
This pull request has been tagged as a stable backport. It will be cherry-picked into the next stable point release.
|
||||||
|
|
||||||
|
Please observe the following:
|
||||||
|
|
||||||
|
* Any dependent PRs that this PR requires **must** be tagged for stable backporting as well.
|
||||||
|
|
||||||
|
* Any issue(s) this PR fixes or closes **should** target the current stable release or a previous stable release to which a fix has not yet entered the current stable release.
|
||||||
|
|
||||||
|
* This PR **must** be test cherry-picked against the current release branch (`release-X.Y.z` where X and Y are numbers). It must apply cleanly, or a diff of the expected change must be provided.
|
||||||
|
|
||||||
|
To do this, run the following commands from your local copy of the Jellyfin repository:
|
||||||
|
|
||||||
|
1. `git checkout master`
|
||||||
|
|
||||||
|
1. `git merge --no-ff <myPullRequestBranch>`
|
||||||
|
|
||||||
|
1. `git log` -> `commit xxxxxxxxx`, grab hash
|
||||||
|
|
||||||
|
1. `git checkout release-X.Y.z` replacing X and Y with the *current* stable version (e.g. `release-10.7.z`)
|
||||||
|
|
||||||
|
1. `git cherry-pick -sx -m1 <hash>`
|
||||||
|
|
||||||
|
Ensure the `cherry-pick` applies cleanly. If it does not, fix any merge conflicts *preserving as much of the original code as possible*, and make note of the resulting diff.
|
||||||
|
|
||||||
|
Test your changes with a build to ensure they are successful. If not, adjust the diff accordingly.
|
||||||
|
|
||||||
|
**Do not** push your merges to either branch. Use `git reset --hard HEAD~1` to revert both branches to their original state.
|
||||||
|
|
||||||
|
Reply to this PR with a comment beginning "Cherry-pick test completed." and including the merge-conflict-fixing diff(s) if applicable.
|
22
.github/workflows/automation.yml
vendored
22
.github/workflows/automation.yml
vendored
@ -2,8 +2,6 @@ name: Automation
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
issues:
|
|
||||||
issue_comment:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
@ -16,31 +14,31 @@ jobs:
|
|||||||
label: stable backport
|
label: stable backport
|
||||||
|
|
||||||
- name: Remove from 'Current Release' project
|
- name: Remove from 'Current Release' project
|
||||||
uses: alex-page/github-project-automation-plus@v0.5.1
|
uses: alex-page/github-project-automation-plus@v0.7.1
|
||||||
if: (github.event.pull_request || github.event.issue.pull_request) && !steps.checkLabel.outputs.hasLabel
|
if: (github.event.pull_request || github.event.issue.pull_request) && !steps.checkLabel.outputs.hasLabel
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
project: Current Release
|
project: Current Release
|
||||||
action: delete
|
action: delete
|
||||||
repo-token: ${{ secrets.GH_TOKEN }}
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
|
||||||
- name: Add to 'Release Next' project
|
- name: Add to 'Release Next' project
|
||||||
uses: alex-page/github-project-automation-plus@v0.5.1
|
uses: alex-page/github-project-automation-plus@v0.7.1
|
||||||
if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
|
if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
project: Release Next
|
project: Release Next
|
||||||
column: In progress
|
column: In progress
|
||||||
repo-token: ${{ secrets.GH_TOKEN }}
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
|
||||||
- name: Add to 'Current Release' project
|
- name: Add to 'Current Release' project
|
||||||
uses: alex-page/github-project-automation-plus@v0.5.1
|
uses: alex-page/github-project-automation-plus@v0.7.1
|
||||||
if: (github.event.pull_request || github.event.issue.pull_request) && steps.checkLabel.outputs.hasLabel
|
if: (github.event.pull_request || github.event.issue.pull_request) && steps.checkLabel.outputs.hasLabel
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
project: Current Release
|
project: Current Release
|
||||||
column: In progress
|
column: In progress
|
||||||
repo-token: ${{ secrets.GH_TOKEN }}
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
|
||||||
- name: Check number of comments from the team member
|
- name: Check number of comments from the team member
|
||||||
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER'
|
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER'
|
||||||
@ -48,19 +46,19 @@ jobs:
|
|||||||
run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)"
|
run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)"
|
||||||
|
|
||||||
- name: Move issue to needs triage
|
- name: Move issue to needs triage
|
||||||
uses: alex-page/github-project-automation-plus@v0.5.1
|
uses: alex-page/github-project-automation-plus@v0.7.1
|
||||||
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
|
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
project: Issue Triage for Main Repo
|
project: Issue Triage for Main Repo
|
||||||
column: Needs triage
|
column: Needs triage
|
||||||
repo-token: ${{ secrets.GH_TOKEN }}
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
|
||||||
- name: Add issue to triage project
|
- name: Add issue to triage project
|
||||||
uses: alex-page/github-project-automation-plus@v0.5.1
|
uses: alex-page/github-project-automation-plus@v0.7.1
|
||||||
if: github.event.issue.pull_request == '' && github.event.action == 'opened'
|
if: github.event.issue.pull_request == '' && github.event.action == 'opened'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
project: Issue Triage for Main Repo
|
project: Issue Triage for Main Repo
|
||||||
column: Pending response
|
column: Pending response
|
||||||
repo-token: ${{ secrets.GH_TOKEN }}
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
22
.github/workflows/label-commenter.yml
vendored
Normal file
22
.github/workflows/label-commenter.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
name: Label Commenter
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types:
|
||||||
|
- labeled
|
||||||
|
- unlabeled
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- labeled
|
||||||
|
- unlabeled
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
comment:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: master
|
||||||
|
|
||||||
|
- name: Label Commenter
|
||||||
|
uses: peaceiris/actions-label-commenter@v1
|
2
.github/workflows/merge-conflicts.yml
vendored
2
.github/workflows/merge-conflicts.yml
vendored
@ -14,4 +14,4 @@ jobs:
|
|||||||
- uses: eps1lon/actions-label-merge-conflict@v2.0.1
|
- uses: eps1lon/actions-label-merge-conflict@v2.0.1
|
||||||
with:
|
with:
|
||||||
dirtyLabel: 'merge conflict'
|
dirtyLabel: 'merge conflict'
|
||||||
repoToken: ${{ secrets.GH_TOKEN }}
|
repoToken: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
6
.github/workflows/rebase.yml
vendored
6
.github/workflows/rebase.yml
vendored
@ -11,17 +11,17 @@ jobs:
|
|||||||
- name: Notify as seen
|
- name: Notify as seen
|
||||||
uses: peter-evans/create-or-update-comment@v1.4.5
|
uses: peter-evans/create-or-update-comment@v1.4.5
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
comment-id: ${{ github.event.comment.id }}
|
comment-id: ${{ github.event.comment.id }}
|
||||||
reactions: '+1'
|
reactions: '+1'
|
||||||
|
|
||||||
- name: Checkout the latest code
|
- name: Checkout the latest code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Automatic Rebase
|
- name: Automatic Rebase
|
||||||
uses: cirrus-actions/rebase@1.4
|
uses: cirrus-actions/rebase@1.4
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
ARG DOTNET_VERSION=5.0
|
ARG DOTNET_VERSION=5.0
|
||||||
|
|
||||||
FROM node:alpine as web-builder
|
FROM node:lts-alpine as web-builder
|
||||||
ARG JELLYFIN_WEB_VERSION=master
|
ARG JELLYFIN_WEB_VERSION=master
|
||||||
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \
|
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
|
||||||
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||||
&& cd jellyfin-web-* \
|
&& cd jellyfin-web-* \
|
||||||
&& npm ci --no-audit \
|
&& npm ci --no-audit --unsafe-perm \
|
||||||
&& mv dist /dist
|
&& mv dist /dist
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
|
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
|
||||||
|
@ -5,12 +5,12 @@
|
|||||||
ARG DOTNET_VERSION=5.0
|
ARG DOTNET_VERSION=5.0
|
||||||
|
|
||||||
|
|
||||||
FROM node:alpine as web-builder
|
FROM node:lts-alpine as web-builder
|
||||||
ARG JELLYFIN_WEB_VERSION=master
|
ARG JELLYFIN_WEB_VERSION=master
|
||||||
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \
|
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
|
||||||
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||||
&& cd jellyfin-web-* \
|
&& cd jellyfin-web-* \
|
||||||
&& npm ci --no-audit \
|
&& npm ci --no-audit --unsafe-perm \
|
||||||
&& mv dist /dist
|
&& mv dist /dist
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,12 +5,12 @@
|
|||||||
ARG DOTNET_VERSION=5.0
|
ARG DOTNET_VERSION=5.0
|
||||||
|
|
||||||
|
|
||||||
FROM node:alpine as web-builder
|
FROM node:lts-alpine as web-builder
|
||||||
ARG JELLYFIN_WEB_VERSION=master
|
ARG JELLYFIN_WEB_VERSION=master
|
||||||
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \
|
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
|
||||||
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||||
&& cd jellyfin-web-* \
|
&& cd jellyfin-web-* \
|
||||||
&& npm ci --no-audit \
|
&& npm ci --no-audit --unsafe-perm \
|
||||||
&& mv dist /dist
|
&& mv dist /dist
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
namespace Emby.Dlna.Configuration
|
namespace Emby.Dlna.Configuration
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
#nullable enable
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using Emby.Dlna.Configuration;
|
using Emby.Dlna.Configuration;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -976,15 +978,28 @@ namespace Emby.Dlna.Didl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg");
|
// TODO: Remove these default values
|
||||||
|
var albumArtUrlInfo = GetImageUrl(
|
||||||
|
imageInfo,
|
||||||
|
_profile.MaxAlbumArtWidth ?? 10000,
|
||||||
|
_profile.MaxAlbumArtHeight ?? 10000,
|
||||||
|
"jpg");
|
||||||
|
|
||||||
writer.WriteStartElement("upnp", "albumArtURI", NsUpnp);
|
writer.WriteStartElement("upnp", "albumArtURI", NsUpnp);
|
||||||
|
if (!string.IsNullOrEmpty(_profile.AlbumArtPn))
|
||||||
|
{
|
||||||
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
|
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
|
||||||
writer.WriteString(albumartUrlInfo.url);
|
}
|
||||||
|
|
||||||
|
writer.WriteString(albumArtUrlInfo.url);
|
||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
|
|
||||||
// TOOD: Remove these default values
|
// TODO: Remove these default values
|
||||||
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg");
|
var iconUrlInfo = GetImageUrl(
|
||||||
|
imageInfo,
|
||||||
|
_profile.MaxIconWidth ?? 48,
|
||||||
|
_profile.MaxIconHeight ?? 48,
|
||||||
|
"jpg");
|
||||||
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url);
|
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url);
|
||||||
|
|
||||||
if (!_profile.EnableAlbumArtInDidl)
|
if (!_profile.EnableAlbumArtInDidl)
|
||||||
@ -1207,8 +1222,7 @@ namespace Emby.Dlna.Didl
|
|||||||
|
|
||||||
if (width.HasValue && height.HasValue)
|
if (width.HasValue && height.HasValue)
|
||||||
{
|
{
|
||||||
var newSize = DrawingUtils.Resize(
|
var newSize = DrawingUtils.Resize(new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
|
||||||
new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
|
|
||||||
|
|
||||||
width = newSize.Width;
|
width = newSize.Width;
|
||||||
height = newSize.Height;
|
height = newSize.Height;
|
||||||
|
@ -9,7 +9,7 @@ namespace Emby.Dlna.Didl
|
|||||||
{
|
{
|
||||||
public class StringWriterWithEncoding : StringWriter
|
public class StringWriterWithEncoding : StringWriter
|
||||||
{
|
{
|
||||||
private readonly Encoding _encoding;
|
private readonly Encoding? _encoding;
|
||||||
|
|
||||||
public StringWriterWithEncoding()
|
public StringWriterWithEncoding()
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
#nullable enable
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -111,7 +113,7 @@ namespace Emby.Dlna
|
|||||||
|
|
||||||
if (profile != null)
|
if (profile != null)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Found matching device profile: {0}", profile.Name);
|
_logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -138,80 +140,45 @@ namespace Emby.Dlna
|
|||||||
_logger.LogInformation(builder.ToString());
|
_logger.LogInformation(builder.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
|
/// <summary>
|
||||||
|
/// Attempts to match a device with a profile.
|
||||||
|
/// Rules:
|
||||||
|
/// - If the profile field has no value, the field matches irregardless of its contents.
|
||||||
|
/// - the profile field can be an exact match, or a reg exp.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deviceInfo">The <see cref="DeviceIdentification"/> of the device.</param>
|
||||||
|
/// <param name="profileInfo">The <see cref="DeviceIdentification"/> of the profile.</param>
|
||||||
|
/// <returns><b>True</b> if they match.</returns>
|
||||||
|
public bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
|
return IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)
|
||||||
{
|
&& IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)
|
||||||
if (deviceInfo.FriendlyName == null || !IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
|
&& IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)
|
||||||
{
|
&& IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)
|
||||||
return false;
|
&& IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)
|
||||||
}
|
&& IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)
|
||||||
}
|
&& IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)
|
||||||
|
&& IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber);
|
||||||
if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
|
|
||||||
{
|
|
||||||
if (deviceInfo.Manufacturer == null || !IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
|
|
||||||
{
|
|
||||||
if (deviceInfo.ManufacturerUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
|
|
||||||
{
|
|
||||||
if (deviceInfo.ModelDescription == null || !IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(profileInfo.ModelName))
|
|
||||||
{
|
|
||||||
if (deviceInfo.ModelName == null || !IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
|
|
||||||
{
|
|
||||||
if (deviceInfo.ModelNumber == null || !IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
|
|
||||||
{
|
|
||||||
if (deviceInfo.ModelUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
|
|
||||||
{
|
|
||||||
if (deviceInfo.SerialNumber == null || !IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsRegexOrSubstringMatch(string input, string pattern)
|
private bool IsRegexOrSubstringMatch(string input, string pattern)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrEmpty(pattern))
|
||||||
|
{
|
||||||
|
// In profile identification: An empty pattern matches anything.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(input))
|
||||||
|
{
|
||||||
|
// The profile contains a value, and the device doesn't.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return input.Contains(pattern, StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
return input.Equals(pattern, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||||
}
|
}
|
||||||
catch (ArgumentException ex)
|
catch (ArgumentException ex)
|
||||||
{
|
{
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers-->
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -188,7 +190,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
|
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
|
||||||
|
|
||||||
string serverAddress = _appHost.GetSmartApiUrl(info.LocalIpAddress);
|
string serverAddress = _appHost.GetSmartApiUrl(info.RemoteIpAddress);
|
||||||
|
|
||||||
controller = new PlayToController(
|
controller = new PlayToController(
|
||||||
sessionInfo,
|
sessionInfo,
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -46,7 +46,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var serviceAction = new ServiceAction
|
var serviceAction = new ServiceAction
|
||||||
{
|
{
|
||||||
Name = container.GetValue(UPnpNamespaces.Svc + "name"),
|
Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
var argumentList = serviceAction.ArgumentList;
|
var argumentList = serviceAction.ArgumentList;
|
||||||
@ -68,9 +68,9 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
return new Argument
|
return new Argument
|
||||||
{
|
{
|
||||||
Name = container.GetValue(UPnpNamespaces.Svc + "name"),
|
Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty,
|
||||||
Direction = container.GetValue(UPnpNamespaces.Svc + "direction"),
|
Direction = container.GetValue(UPnpNamespaces.Svc + "direction") ?? string.Empty,
|
||||||
RelatedStateVariable = container.GetValue(UPnpNamespaces.Svc + "relatedStateVariable")
|
RelatedStateVariable = container.GetValue(UPnpNamespaces.Svc + "relatedStateVariable") ?? string.Empty
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,8 +89,8 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
return new StateVariable
|
return new StateVariable
|
||||||
{
|
{
|
||||||
Name = container.GetValue(UPnpNamespaces.Svc + "name"),
|
Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty,
|
||||||
DataType = container.GetValue(UPnpNamespaces.Svc + "dataType"),
|
DataType = container.GetValue(UPnpNamespaces.Svc + "dataType") ?? string.Empty,
|
||||||
AllowedValues = allowedValues
|
AllowedValues = allowedValues
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -166,7 +166,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamesapce, stateString);
|
return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamesapce, stateString);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildArgumentXml(Argument argument, string value, string commandParameter = "")
|
private string BuildArgumentXml(Argument argument, string? value, string commandParameter = "")
|
||||||
{
|
{
|
||||||
var state = StateVariables.FirstOrDefault(a => string.Equals(a.Name, argument.RelatedStateVariable, StringComparison.OrdinalIgnoreCase));
|
var state = StateVariables.FirstOrDefault(a => string.Equals(a.Name, argument.RelatedStateVariable, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -250,7 +250,8 @@ namespace Emby.Dlna.Server
|
|||||||
|
|
||||||
url = _serverAddress.TrimEnd('/') + "/dlna/" + _serverUdn + "/" + url.TrimStart('/');
|
url = _serverAddress.TrimEnd('/') + "/dlna/" + _serverUdn + "/" + url.TrimStart('/');
|
||||||
|
|
||||||
return SecurityElement.Escape(url);
|
// TODO: @bond remove null-coalescing operator when https://github.com/dotnet/runtime/pull/52442 is merged/released
|
||||||
|
return SecurityElement.Escape(url) ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<DeviceIcon> GetIcons()
|
private IEnumerable<DeviceIcon> GetIcons()
|
||||||
|
@ -47,7 +47,7 @@ namespace Emby.Dlna.Service
|
|||||||
|
|
||||||
private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request)
|
private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request)
|
||||||
{
|
{
|
||||||
ControlRequestInfo requestInfo = null;
|
ControlRequestInfo? requestInfo = null;
|
||||||
|
|
||||||
using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8))
|
using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8))
|
||||||
{
|
{
|
||||||
@ -151,7 +151,7 @@ namespace Emby.Dlna.Service
|
|||||||
|
|
||||||
private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader)
|
private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader)
|
||||||
{
|
{
|
||||||
string namespaceURI = null, localName = null;
|
string? namespaceURI = null, localName = null;
|
||||||
|
|
||||||
await reader.MoveToContentAsync().ConfigureAwait(false);
|
await reader.MoveToContentAsync().ConfigureAwait(false);
|
||||||
await reader.ReadAsync().ConfigureAwait(false);
|
await reader.ReadAsync().ConfigureAwait(false);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -104,7 +106,7 @@ namespace Emby.Dlna.Ssdp
|
|||||||
{
|
{
|
||||||
Location = e.DiscoveredDevice.DescriptionLocation,
|
Location = e.DiscoveredDevice.DescriptionLocation,
|
||||||
Headers = headers,
|
Headers = headers,
|
||||||
LocalIpAddress = e.LocalIpAddress
|
RemoteIpAddress = e.RemoteIpAddress
|
||||||
});
|
});
|
||||||
|
|
||||||
DeviceDiscoveredInternal?.Invoke(this, args);
|
DeviceDiscoveredInternal?.Invoke(this, args);
|
||||||
|
@ -7,21 +7,21 @@ namespace Emby.Dlna.Ssdp
|
|||||||
{
|
{
|
||||||
public static class SsdpExtensions
|
public static class SsdpExtensions
|
||||||
{
|
{
|
||||||
public static string GetValue(this XElement container, XName name)
|
public static string? GetValue(this XElement container, XName name)
|
||||||
{
|
{
|
||||||
var node = container.Element(name);
|
var node = container.Element(name);
|
||||||
|
|
||||||
return node?.Value;
|
return node?.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetAttributeValue(this XElement container, XName name)
|
public static string? GetAttributeValue(this XElement container, XName name)
|
||||||
{
|
{
|
||||||
var node = container.Attribute(name);
|
var node = container.Attribute(name);
|
||||||
|
|
||||||
return node?.Value;
|
return node?.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetDescendantValue(this XElement container, XName name)
|
public static string? GetDescendantValue(this XElement container, XName name)
|
||||||
=> container.Descendants(name).FirstOrDefault()?.Value;
|
=> container.Descendants(name).FirstOrDefault()?.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
|
|
||||||
namespace Emby.Naming.Audio
|
namespace Emby.Naming.Audio
|
||||||
{
|
{
|
||||||
@ -18,8 +18,8 @@ namespace Emby.Naming.Audio
|
|||||||
/// <returns>True if file at path is audio file.</returns>
|
/// <returns>True if file at path is audio file.</returns>
|
||||||
public static bool IsAudioFile(string path, NamingOptions options)
|
public static bool IsAudioFile(string path, NamingOptions options)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path.AsSpan());
|
||||||
return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
return options.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,11 +23,12 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\SharedVersion.cs" />
|
<Compile Include="../SharedVersion.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
<ProjectReference Include="../MediaBrowser.Common/MediaBrowser.Common.csproj" />
|
||||||
|
<ProjectReference Include="../MediaBrowser.Model/MediaBrowser.Model.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
@ -68,6 +68,11 @@ namespace Emby.Naming.TV
|
|||||||
var parsingResult = new EpisodePathParser(_options)
|
var parsingResult = new EpisodePathParser(_options)
|
||||||
.Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo);
|
.Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo);
|
||||||
|
|
||||||
|
if (!parsingResult.Success && !isStub)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return new EpisodeInfo(path)
|
return new EpisodeInfo(path)
|
||||||
{
|
{
|
||||||
Container = container,
|
Container = container,
|
||||||
|
@ -29,36 +29,33 @@ namespace Emby.Naming.Video
|
|||||||
/// <param name="path">Path to file.</param>
|
/// <param name="path">Path to file.</param>
|
||||||
/// <returns>Returns <see cref="ExtraResult"/> object.</returns>
|
/// <returns>Returns <see cref="ExtraResult"/> object.</returns>
|
||||||
public ExtraResult GetExtraInfo(string path)
|
public ExtraResult GetExtraInfo(string path)
|
||||||
{
|
|
||||||
return _options.VideoExtraRules
|
|
||||||
.Select(i => GetExtraInfo(path, i))
|
|
||||||
.FirstOrDefault(i => i.ExtraType != null) ?? new ExtraResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExtraResult GetExtraInfo(string path, ExtraRule rule)
|
|
||||||
{
|
{
|
||||||
var result = new ExtraResult();
|
var result = new ExtraResult();
|
||||||
|
|
||||||
|
for (var i = 0; i < _options.VideoExtraRules.Length; i++)
|
||||||
|
{
|
||||||
|
var rule = _options.VideoExtraRules[i];
|
||||||
if (rule.MediaType == MediaType.Audio)
|
if (rule.MediaType == MediaType.Audio)
|
||||||
{
|
{
|
||||||
if (!AudioFileParser.IsAudioFile(path, _options))
|
if (!AudioFileParser.IsAudioFile(path, _options))
|
||||||
{
|
{
|
||||||
return result;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (rule.MediaType == MediaType.Video)
|
else if (rule.MediaType == MediaType.Video)
|
||||||
{
|
{
|
||||||
if (!new VideoResolver(_options).IsVideoFile(path))
|
if (!new VideoResolver(_options).IsVideoFile(path))
|
||||||
{
|
{
|
||||||
return result;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pathSpan = path.AsSpan();
|
||||||
if (rule.RuleType == ExtraRuleType.Filename)
|
if (rule.RuleType == ExtraRuleType.Filename)
|
||||||
{
|
{
|
||||||
var filename = Path.GetFileNameWithoutExtension(path);
|
var filename = Path.GetFileNameWithoutExtension(pathSpan);
|
||||||
|
|
||||||
if (string.Equals(filename, rule.Token, StringComparison.OrdinalIgnoreCase))
|
if (filename.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
result.ExtraType = rule.ExtraType;
|
result.ExtraType = rule.ExtraType;
|
||||||
result.Rule = rule;
|
result.Rule = rule;
|
||||||
@ -66,9 +63,9 @@ namespace Emby.Naming.Video
|
|||||||
}
|
}
|
||||||
else if (rule.RuleType == ExtraRuleType.Suffix)
|
else if (rule.RuleType == ExtraRuleType.Suffix)
|
||||||
{
|
{
|
||||||
var filename = Path.GetFileNameWithoutExtension(path);
|
var filename = Path.GetFileNameWithoutExtension(pathSpan);
|
||||||
|
|
||||||
if (filename.IndexOf(rule.Token, StringComparison.OrdinalIgnoreCase) > 0)
|
if (filename.Contains(rule.Token, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
result.ExtraType = rule.ExtraType;
|
result.ExtraType = rule.ExtraType;
|
||||||
result.Rule = rule;
|
result.Rule = rule;
|
||||||
@ -88,14 +85,20 @@ namespace Emby.Naming.Video
|
|||||||
}
|
}
|
||||||
else if (rule.RuleType == ExtraRuleType.DirectoryName)
|
else if (rule.RuleType == ExtraRuleType.DirectoryName)
|
||||||
{
|
{
|
||||||
var directoryName = Path.GetFileName(Path.GetDirectoryName(path));
|
var directoryName = Path.GetFileName(Path.GetDirectoryName(pathSpan));
|
||||||
if (string.Equals(directoryName, rule.Token, StringComparison.OrdinalIgnoreCase))
|
if (directoryName.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
result.ExtraType = rule.ExtraType;
|
result.ExtraType = rule.ExtraType;
|
||||||
result.Rule = rule;
|
result.Rule = rule;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result.ExtraType != null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
@ -59,15 +59,15 @@ namespace Emby.Naming.Video
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isStub = false;
|
bool isStub = false;
|
||||||
string? container = null;
|
ReadOnlySpan<char> container = ReadOnlySpan<char>.Empty;
|
||||||
string? stubType = null;
|
string? stubType = null;
|
||||||
|
|
||||||
if (!isDirectory)
|
if (!isDirectory)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path.AsSpan());
|
||||||
|
|
||||||
// Check supported extensions
|
// Check supported extensions
|
||||||
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
if (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// It's not supported. Check stub extensions
|
// It's not supported. Check stub extensions
|
||||||
if (!StubResolver.TryResolveFile(path, _options, out stubType))
|
if (!StubResolver.TryResolveFile(path, _options, out stubType))
|
||||||
@ -86,9 +86,7 @@ namespace Emby.Naming.Video
|
|||||||
|
|
||||||
var extraResult = new ExtraResolver(_options).GetExtraInfo(path);
|
var extraResult = new ExtraResolver(_options).GetExtraInfo(path);
|
||||||
|
|
||||||
var name = isDirectory
|
var name = Path.GetFileNameWithoutExtension(path);
|
||||||
? Path.GetFileName(path)
|
|
||||||
: Path.GetFileNameWithoutExtension(path);
|
|
||||||
|
|
||||||
int? year = null;
|
int? year = null;
|
||||||
|
|
||||||
@ -107,7 +105,7 @@ namespace Emby.Naming.Video
|
|||||||
|
|
||||||
return new VideoFileInfo(
|
return new VideoFileInfo(
|
||||||
path: path,
|
path: path,
|
||||||
container: container,
|
container: container.IsEmpty ? null : container.ToString(),
|
||||||
isStub: isStub,
|
isStub: isStub,
|
||||||
name: name,
|
name: name,
|
||||||
year: year,
|
year: year,
|
||||||
@ -126,8 +124,8 @@ namespace Emby.Naming.Video
|
|||||||
/// <returns>True if is video file.</returns>
|
/// <returns>True if is video file.</returns>
|
||||||
public bool IsVideoFile(string path)
|
public bool IsVideoFile(string path)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path.AsSpan());
|
||||||
return _options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
return _options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -137,8 +135,8 @@ namespace Emby.Naming.Video
|
|||||||
/// <returns>True if is video file stub.</returns>
|
/// <returns>True if is video file stub.</returns>
|
||||||
public bool IsStubFile(string path)
|
public bool IsStubFile(string path)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path.AsSpan());
|
||||||
return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
return _options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
CachePath = cacheDirectoryPath;
|
CachePath = cacheDirectoryPath;
|
||||||
WebPath = webDirectoryPath;
|
WebPath = webDirectoryPath;
|
||||||
|
|
||||||
DataPath = Path.Combine(ProgramDataPath, "data");
|
_dataPath = Directory.CreateDirectory(Path.Combine(ProgramDataPath, "data")).FullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -55,11 +55,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
/// Gets the folder path to the data directory.
|
/// Gets the folder path to the data directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The data directory.</value>
|
/// <value>The data directory.</value>
|
||||||
public string DataPath
|
public string DataPath => _dataPath;
|
||||||
{
|
|
||||||
get => _dataPath;
|
|
||||||
private set => _dataPath = Directory.CreateDirectory(value).FullName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string VirtualDataPath => "%AppDataPath%";
|
public string VirtualDataPath => "%AppDataPath%";
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable enable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -335,10 +337,7 @@ namespace Emby.Server.Implementations
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_deviceId == null)
|
_deviceId ??= new DeviceId(ApplicationPaths, LoggerFactory);
|
||||||
{
|
|
||||||
_deviceId = new DeviceId(ApplicationPaths, LoggerFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _deviceId.Value;
|
return _deviceId.Value;
|
||||||
}
|
}
|
||||||
@ -370,10 +369,7 @@ namespace Emby.Server.Implementations
|
|||||||
/// <returns>System.Object.</returns>
|
/// <returns>System.Object.</returns>
|
||||||
protected object CreateInstanceSafe(Type type)
|
protected object CreateInstanceSafe(Type type)
|
||||||
{
|
{
|
||||||
if (_creatingInstances == null)
|
_creatingInstances ??= new List<Type>();
|
||||||
{
|
|
||||||
_creatingInstances = new List<Type>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_creatingInstances.IndexOf(type) != -1)
|
if (_creatingInstances.IndexOf(type) != -1)
|
||||||
{
|
{
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
@ -82,9 +82,9 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
.Where(i => i != null)
|
.Where(i => i != null)
|
||||||
.GroupBy(x => x.Id)
|
.GroupBy(x => x!.Id) // We removed the null values
|
||||||
.Select(x => x.First())
|
.Select(x => x.First())
|
||||||
.ToList();
|
.ToList()!; // Again... the list doesn't contain any null values
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@ -164,7 +166,7 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
|
|
||||||
parentFolder.AddChild(collection, CancellationToken.None);
|
parentFolder.AddChild(collection, CancellationToken.None);
|
||||||
|
|
||||||
if (options.ItemIdList.Length > 0)
|
if (options.ItemIdList.Count > 0)
|
||||||
{
|
{
|
||||||
await AddToCollectionAsync(
|
await AddToCollectionAsync(
|
||||||
collection.Id,
|
collection.Id,
|
||||||
@ -248,11 +250,7 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
|
|
||||||
if (fireEvent)
|
if (fireEvent)
|
||||||
{
|
{
|
||||||
ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs
|
ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList));
|
||||||
{
|
|
||||||
Collection = collection,
|
|
||||||
ItemsChanged = itemList
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,11 +302,7 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
},
|
},
|
||||||
RefreshPriority.High);
|
RefreshPriority.High);
|
||||||
|
|
||||||
ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs
|
ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList));
|
||||||
{
|
|
||||||
Collection = collection,
|
|
||||||
ItemsChanged = itemList
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable enable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -181,11 +183,9 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
foreach (var row in connection.Query("PRAGMA table_info(" + table + ")"))
|
foreach (var row in connection.Query("PRAGMA table_info(" + table + ")"))
|
||||||
{
|
{
|
||||||
if (row[1].SQLiteType != SQLiteType.Null)
|
if (row.TryGetString(1, out var columnName))
|
||||||
{
|
{
|
||||||
var name = row[1].ToString();
|
columnNames.Add(columnName);
|
||||||
|
|
||||||
columnNames.Add(name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
{
|
{
|
||||||
public class ManagedConnection : IDisposable
|
public class ManagedConnection : IDisposable
|
||||||
{
|
{
|
||||||
private SQLiteDatabaseConnection _db;
|
private SQLiteDatabaseConnection? _db;
|
||||||
private readonly SemaphoreSlim _writeLock;
|
private readonly SemaphoreSlim _writeLock;
|
||||||
private bool _disposed = false;
|
private bool _disposed = false;
|
||||||
|
|
||||||
@ -54,12 +54,12 @@ namespace Emby.Server.Implementations.Data
|
|||||||
return _db.RunInTransaction(action, mode);
|
return _db.RunInTransaction(action, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<IReadOnlyList<IResultSetValue>> Query(string sql)
|
public IEnumerable<IReadOnlyList<ResultSetValue>> Query(string sql)
|
||||||
{
|
{
|
||||||
return _db.Query(sql);
|
return _db.Query(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<IReadOnlyList<IResultSetValue>> Query(string sql, params object[] values)
|
public IEnumerable<IReadOnlyList<ResultSetValue>> Query(string sql, params object[] values)
|
||||||
{
|
{
|
||||||
return _db.Query(sql, values);
|
return _db.Query(sql, values);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#nullable disable
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Guid ReadGuidFromBlob(this IResultSetValue result)
|
public static Guid ReadGuidFromBlob(this ResultSetValue result)
|
||||||
{
|
{
|
||||||
return new Guid(result.ToBlob());
|
return new Guid(result.ToBlob());
|
||||||
}
|
}
|
||||||
@ -85,7 +86,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
private static string GetDateTimeKindFormat(DateTimeKind kind)
|
private static string GetDateTimeKindFormat(DateTimeKind kind)
|
||||||
=> (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal;
|
=> (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal;
|
||||||
|
|
||||||
public static DateTime ReadDateTime(this IResultSetValue result)
|
public static DateTime ReadDateTime(this ResultSetValue result)
|
||||||
{
|
{
|
||||||
var dateText = result.ToString();
|
var dateText = result.ToString();
|
||||||
|
|
||||||
@ -96,49 +97,139 @@ namespace Emby.Server.Implementations.Data
|
|||||||
DateTimeStyles.None).ToUniversalTime();
|
DateTimeStyles.None).ToUniversalTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DateTime? TryReadDateTime(this IResultSetValue result)
|
public static bool TryReadDateTime(this IReadOnlyList<ResultSetValue> reader, int index, out DateTime result)
|
||||||
{
|
{
|
||||||
var dateText = result.ToString();
|
var item = reader[index];
|
||||||
|
if (item.IsDbNull())
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dateText = item.ToString();
|
||||||
|
|
||||||
if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var dateTimeResult))
|
if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var dateTimeResult))
|
||||||
{
|
{
|
||||||
return dateTimeResult.ToUniversalTime();
|
result = dateTimeResult.ToUniversalTime();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
result = default;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsDBNull(this IReadOnlyList<IResultSetValue> result, int index)
|
public static bool TryGetGuid(this IReadOnlyList<ResultSetValue> reader, int index, out Guid result)
|
||||||
{
|
{
|
||||||
return result[index].SQLiteType == SQLiteType.Null;
|
var item = reader[index];
|
||||||
|
if (item.IsDbNull())
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetString(this IReadOnlyList<IResultSetValue> result, int index)
|
result = item.ReadGuidFromBlob();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsDbNull(this ResultSetValue result)
|
||||||
|
{
|
||||||
|
return result.SQLiteType == SQLiteType.Null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetString(this IReadOnlyList<ResultSetValue> result, int index)
|
||||||
{
|
{
|
||||||
return result[index].ToString();
|
return result[index].ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool GetBoolean(this IReadOnlyList<IResultSetValue> result, int index)
|
public static bool TryGetString(this IReadOnlyList<ResultSetValue> reader, int index, out string result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
var item = reader[index];
|
||||||
|
if (item.IsDbNull())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = item.ToString();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool GetBoolean(this IReadOnlyList<ResultSetValue> result, int index)
|
||||||
{
|
{
|
||||||
return result[index].ToBool();
|
return result[index].ToBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int GetInt32(this IReadOnlyList<IResultSetValue> result, int index)
|
public static bool TryGetBoolean(this IReadOnlyList<ResultSetValue> reader, int index, out bool result)
|
||||||
{
|
{
|
||||||
return result[index].ToInt();
|
var item = reader[index];
|
||||||
|
if (item.IsDbNull())
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long GetInt64(this IReadOnlyList<IResultSetValue> result, int index)
|
result = item.ToBool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetInt32(this IReadOnlyList<ResultSetValue> reader, int index, out int result)
|
||||||
|
{
|
||||||
|
var item = reader[index];
|
||||||
|
if (item.IsDbNull())
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = item.ToInt();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long GetInt64(this IReadOnlyList<ResultSetValue> result, int index)
|
||||||
{
|
{
|
||||||
return result[index].ToInt64();
|
return result[index].ToInt64();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float GetFloat(this IReadOnlyList<IResultSetValue> result, int index)
|
public static bool TryGetInt64(this IReadOnlyList<ResultSetValue> reader, int index, out long result)
|
||||||
{
|
{
|
||||||
return result[index].ToFloat();
|
var item = reader[index];
|
||||||
|
if (item.IsDbNull())
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Guid GetGuid(this IReadOnlyList<IResultSetValue> result, int index)
|
result = item.ToInt64();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetSingle(this IReadOnlyList<ResultSetValue> reader, int index, out float result)
|
||||||
|
{
|
||||||
|
var item = reader[index];
|
||||||
|
if (item.IsDbNull())
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = item.ToFloat();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetDouble(this IReadOnlyList<ResultSetValue> reader, int index, out double result)
|
||||||
|
{
|
||||||
|
var item = reader[index];
|
||||||
|
if (item.IsDbNull())
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = item.ToDouble();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Guid GetGuid(this IReadOnlyList<ResultSetValue> result, int index)
|
||||||
{
|
{
|
||||||
return result[index].ReadGuidFromBlob();
|
return result[index].ReadGuidFromBlob();
|
||||||
}
|
}
|
||||||
@ -350,7 +441,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement statement)
|
public static IEnumerable<IReadOnlyList<ResultSetValue>> ExecuteQuery(this IStatement statement)
|
||||||
{
|
{
|
||||||
while (statement.MoveNext())
|
while (statement.MoveNext())
|
||||||
{
|
{
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -348,16 +350,16 @@ namespace Emby.Server.Implementations.Data
|
|||||||
/// Read a row from the specified reader into the provided userData object.
|
/// Read a row from the specified reader into the provided userData object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="reader"></param>
|
/// <param name="reader"></param>
|
||||||
private UserItemData ReadRow(IReadOnlyList<IResultSetValue> reader)
|
private UserItemData ReadRow(IReadOnlyList<ResultSetValue> reader)
|
||||||
{
|
{
|
||||||
var userData = new UserItemData();
|
var userData = new UserItemData();
|
||||||
|
|
||||||
userData.Key = reader[0].ToString();
|
userData.Key = reader[0].ToString();
|
||||||
// userData.UserId = reader[1].ReadGuidFromBlob();
|
// userData.UserId = reader[1].ReadGuidFromBlob();
|
||||||
|
|
||||||
if (reader[2].SQLiteType != SQLiteType.Null)
|
if (reader.TryGetDouble(2, out var rating))
|
||||||
{
|
{
|
||||||
userData.Rating = reader[2].ToDouble();
|
userData.Rating = rating;
|
||||||
}
|
}
|
||||||
|
|
||||||
userData.Played = reader[3].ToBool();
|
userData.Played = reader[3].ToBool();
|
||||||
@ -365,19 +367,19 @@ namespace Emby.Server.Implementations.Data
|
|||||||
userData.IsFavorite = reader[5].ToBool();
|
userData.IsFavorite = reader[5].ToBool();
|
||||||
userData.PlaybackPositionTicks = reader[6].ToInt64();
|
userData.PlaybackPositionTicks = reader[6].ToInt64();
|
||||||
|
|
||||||
if (reader[7].SQLiteType != SQLiteType.Null)
|
if (reader.TryReadDateTime(7, out var lastPlayedDate))
|
||||||
{
|
{
|
||||||
userData.LastPlayedDate = reader[7].TryReadDateTime();
|
userData.LastPlayedDate = lastPlayedDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader[8].SQLiteType != SQLiteType.Null)
|
if (reader.TryGetInt32(8, out var audioStreamIndex))
|
||||||
{
|
{
|
||||||
userData.AudioStreamIndex = reader[8].ToInt();
|
userData.AudioStreamIndex = audioStreamIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader[9].SQLiteType != SQLiteType.Null)
|
if (reader.TryGetInt32(9, out var subtitleStreamIndex))
|
||||||
{
|
{
|
||||||
userData.SubtitleStreamIndex = reader[9].ToInt();
|
userData.SubtitleStreamIndex = subtitleStreamIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
return userData;
|
return userData;
|
||||||
|
@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
/// This holds all the types in the running assemblies
|
/// This holds all the types in the running assemblies
|
||||||
/// so that we can de-serialize properly when we don't have strong types.
|
/// so that we can de-serialize properly when we don't have strong types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ConcurrentDictionary<string, Type> _typeMap = new ConcurrentDictionary<string, Type>();
|
private readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the type.
|
/// Gets the type.
|
||||||
@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
/// <param name="typeName">Name of the type.</param>
|
/// <param name="typeName">Name of the type.</param>
|
||||||
/// <returns>Type.</returns>
|
/// <returns>Type.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><c>typeName</c> is null.</exception>
|
/// <exception cref="ArgumentNullException"><c>typeName</c> is null.</exception>
|
||||||
public Type GetType(string typeName)
|
public Type? GetType(string typeName)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(typeName))
|
if (string.IsNullOrEmpty(typeName))
|
||||||
{
|
{
|
||||||
@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="typeName">Name of the type.</param>
|
/// <param name="typeName">Name of the type.</param>
|
||||||
/// <returns>Type.</returns>
|
/// <returns>Type.</returns>
|
||||||
private Type LookupType(string typeName)
|
private Type? LookupType(string typeName)
|
||||||
{
|
{
|
||||||
return AppDomain.CurrentDomain.GetAssemblies()
|
return AppDomain.CurrentDomain.GetAssemblies()
|
||||||
.Select(a => a.GetType(typeName))
|
.Select(a => a.GetType(typeName))
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -665,10 +667,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
var tag = GetImageCacheTag(item, image);
|
var tag = GetImageCacheTag(item, image);
|
||||||
if (!string.IsNullOrEmpty(image.BlurHash))
|
if (!string.IsNullOrEmpty(image.BlurHash))
|
||||||
{
|
{
|
||||||
if (dto.ImageBlurHashes == null)
|
dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>();
|
||||||
{
|
|
||||||
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dto.ImageBlurHashes.ContainsKey(image.Type))
|
if (!dto.ImageBlurHashes.ContainsKey(image.Type))
|
||||||
{
|
{
|
||||||
@ -702,10 +701,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
|
|
||||||
if (hashes.Count > 0)
|
if (hashes.Count > 0)
|
||||||
{
|
{
|
||||||
if (dto.ImageBlurHashes == null)
|
dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>();
|
||||||
{
|
|
||||||
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
dto.ImageBlurHashes[imageType] = hashes;
|
dto.ImageBlurHashes[imageType] = hashes;
|
||||||
}
|
}
|
||||||
@ -898,10 +894,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
dto.Taglines = new string[] { item.Tagline };
|
dto.Taglines = new string[] { item.Tagline };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dto.Taglines == null)
|
dto.Taglines ??= Array.Empty<string>();
|
||||||
{
|
|
||||||
dto.Taglines = Array.Empty<string>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dto.Type = item.GetBaseItemKind();
|
dto.Type = item.GetBaseItemKind();
|
||||||
|
@ -27,11 +27,11 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.6" />
|
||||||
<PackageReference Include="Mono.Nat" Version="3.0.1" />
|
<PackageReference Include="Mono.Nat" Version="3.0.1" />
|
||||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.1" />
|
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" />
|
||||||
<PackageReference Include="sharpcompress" Version="0.28.1" />
|
<PackageReference Include="sharpcompress" Version="0.28.2" />
|
||||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.0.1" />
|
||||||
<PackageReference Include="DotNet.Glob" Version="3.1.2" />
|
<PackageReference Include="DotNet.Glob" Version="3.1.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@ -44,6 +44,7 @@
|
|||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
<!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 -->
|
<!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 -->
|
||||||
<NoWarn>AD0001</NoWarn>
|
<NoWarn>AD0001</NoWarn>
|
||||||
<AnalysisMode Condition=" '$(Configuration)' == 'Debug' ">AllEnabledByDefault</AnalysisMode>
|
<AnalysisMode Condition=" '$(Configuration)' == 'Debug' ">AllEnabledByDefault</AnalysisMode>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable enable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Security;
|
using MediaBrowser.Controller.Security;
|
||||||
@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
{
|
{
|
||||||
if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached))
|
if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached))
|
||||||
{
|
{
|
||||||
return (AuthorizationInfo)cached;
|
return (AuthorizationInfo)cached!; // Cache should never contain null
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetAuthorization(requestContext);
|
return GetAuthorization(requestContext);
|
||||||
@ -55,15 +55,15 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
}
|
}
|
||||||
|
|
||||||
private AuthorizationInfo GetAuthorizationInfoFromDictionary(
|
private AuthorizationInfo GetAuthorizationInfoFromDictionary(
|
||||||
in Dictionary<string, string> auth,
|
in Dictionary<string, string>? auth,
|
||||||
in IHeaderDictionary headers,
|
in IHeaderDictionary headers,
|
||||||
in IQueryCollection queryString)
|
in IQueryCollection queryString)
|
||||||
{
|
{
|
||||||
string deviceId = null;
|
string? deviceId = null;
|
||||||
string device = null;
|
string? device = null;
|
||||||
string client = null;
|
string? client = null;
|
||||||
string version = null;
|
string? version = null;
|
||||||
string token = null;
|
string? token = null;
|
||||||
|
|
||||||
if (auth != null)
|
if (auth != null)
|
||||||
{
|
{
|
||||||
@ -206,7 +206,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="httpReq">The HTTP req.</param>
|
/// <param name="httpReq">The HTTP req.</param>
|
||||||
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
||||||
private Dictionary<string, string> GetAuthorizationDictionary(HttpContext httpReq)
|
private Dictionary<string, string>? GetAuthorizationDictionary(HttpContext httpReq)
|
||||||
{
|
{
|
||||||
var auth = httpReq.Request.Headers["X-Emby-Authorization"];
|
var auth = httpReq.Request.Headers["X-Emby-Authorization"];
|
||||||
|
|
||||||
@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
auth = httpReq.Request.Headers[HeaderNames.Authorization];
|
auth = httpReq.Request.Headers[HeaderNames.Authorization];
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetAuthorization(auth);
|
return GetAuthorization(auth.Count > 0 ? auth[0] : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -223,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="httpReq">The HTTP req.</param>
|
/// <param name="httpReq">The HTTP req.</param>
|
||||||
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
||||||
private Dictionary<string, string> GetAuthorizationDictionary(HttpRequest httpReq)
|
private Dictionary<string, string>? GetAuthorizationDictionary(HttpRequest httpReq)
|
||||||
{
|
{
|
||||||
var auth = httpReq.Headers["X-Emby-Authorization"];
|
var auth = httpReq.Headers["X-Emby-Authorization"];
|
||||||
|
|
||||||
@ -232,7 +232,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
auth = httpReq.Headers[HeaderNames.Authorization];
|
auth = httpReq.Headers[HeaderNames.Authorization];
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetAuthorization(auth);
|
return GetAuthorization(auth.Count > 0 ? auth[0] : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -240,43 +240,43 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="authorizationHeader">The authorization header.</param>
|
/// <param name="authorizationHeader">The authorization header.</param>
|
||||||
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
||||||
private Dictionary<string, string> GetAuthorization(string authorizationHeader)
|
private Dictionary<string, string>? GetAuthorization(ReadOnlySpan<char> authorizationHeader)
|
||||||
{
|
{
|
||||||
if (authorizationHeader == null)
|
if (authorizationHeader == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var parts = authorizationHeader.Split(' ', 2);
|
var firstSpace = authorizationHeader.IndexOf(' ');
|
||||||
|
|
||||||
// There should be at least to parts
|
// There should be at least two parts
|
||||||
if (parts.Length != 2)
|
if (firstSpace == -1)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var acceptedNames = new[] { "MediaBrowser", "Emby" };
|
var name = authorizationHeader[..firstSpace];
|
||||||
|
|
||||||
// It has to be a digest request
|
if (!name.Equals("MediaBrowser", StringComparison.OrdinalIgnoreCase)
|
||||||
if (!acceptedNames.Contains(parts[0], StringComparer.OrdinalIgnoreCase))
|
&& !name.Equals("Emby", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove uptil the first space
|
authorizationHeader = authorizationHeader[(firstSpace + 1)..];
|
||||||
authorizationHeader = parts[1];
|
|
||||||
parts = authorizationHeader.Split(',');
|
|
||||||
|
|
||||||
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
foreach (var item in parts)
|
foreach (var item in authorizationHeader.Split(','))
|
||||||
{
|
{
|
||||||
var param = item.Trim().Split('=', 2);
|
var trimmedItem = item.Trim();
|
||||||
|
var firstEqualsSign = trimmedItem.IndexOf('=');
|
||||||
|
|
||||||
if (param.Length == 2)
|
if (firstEqualsSign > 0)
|
||||||
{
|
{
|
||||||
var value = NormalizeValue(param[1].Trim('"'));
|
var key = trimmedItem[..firstEqualsSign].ToString();
|
||||||
result[param[0]] = value;
|
var value = NormalizeValue(trimmedItem[(firstEqualsSign + 1)..].Trim('"').ToString());
|
||||||
|
result[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,14 +36,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
return GetSession((HttpContext)requestContext);
|
return GetSession((HttpContext)requestContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
public User GetUser(HttpContext requestContext)
|
public User? GetUser(HttpContext requestContext)
|
||||||
{
|
{
|
||||||
var session = GetSession(requestContext);
|
var session = GetSession(requestContext);
|
||||||
|
|
||||||
return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
|
return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public User GetUser(object requestContext)
|
public User? GetUser(object requestContext)
|
||||||
{
|
{
|
||||||
return GetUser(((HttpRequest)requestContext).HttpContext);
|
return GetUser(((HttpRequest)requestContext).HttpContext);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable enable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Runtime.InteropServices;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.System;
|
using MediaBrowser.Model.System;
|
||||||
@ -24,7 +23,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
|
|
||||||
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
|
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
|
||||||
private readonly string _tempPath;
|
private readonly string _tempPath;
|
||||||
private readonly bool _isEnvironmentCaseInsensitive;
|
private static readonly bool _isEnvironmentCaseInsensitive = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||||
|
|
||||||
public ManagedFileSystem(
|
public ManagedFileSystem(
|
||||||
ILogger<ManagedFileSystem> logger,
|
ILogger<ManagedFileSystem> logger,
|
||||||
@ -32,8 +31,6 @@ namespace Emby.Server.Implementations.IO
|
|||||||
{
|
{
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
_tempPath = applicationPaths.TempDirectory;
|
_tempPath = applicationPaths.TempDirectory;
|
||||||
|
|
||||||
_isEnvironmentCaseInsensitive = OperatingSystem.Id == OperatingSystemId.Windows;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void AddShortcutHandler(IShortcutHandler handler)
|
public virtual void AddShortcutHandler(IShortcutHandler handler)
|
||||||
@ -55,7 +52,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
var extension = Path.GetExtension(filename);
|
var extension = Path.GetExtension(filename);
|
||||||
return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal));
|
return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -64,7 +61,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
/// <param name="filename">The filename.</param>
|
/// <param name="filename">The filename.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
/// <exception cref="ArgumentNullException">filename</exception>
|
/// <exception cref="ArgumentNullException">filename</exception>
|
||||||
public virtual string ResolveShortcut(string filename)
|
public virtual string? ResolveShortcut(string filename)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(filename))
|
if (string.IsNullOrEmpty(filename))
|
||||||
{
|
{
|
||||||
@ -72,7 +69,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
var extension = Path.GetExtension(filename);
|
var extension = Path.GetExtension(filename);
|
||||||
var handler = _shortcutHandlers.FirstOrDefault(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
|
var handler = _shortcutHandlers.Find(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
return handler?.Resolve(filename);
|
return handler?.Resolve(filename);
|
||||||
}
|
}
|
||||||
@ -263,8 +260,6 @@ namespace Emby.Server.Implementations.IO
|
|||||||
result.Exists = false;
|
result.Exists = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.DirectoryName = fileInfo.DirectoryName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.CreationTimeUtc = GetCreationTimeUtc(info);
|
result.CreationTimeUtc = GetCreationTimeUtc(info);
|
||||||
@ -303,16 +298,37 @@ namespace Emby.Server.Implementations.IO
|
|||||||
/// <param name="filename">The filename.</param>
|
/// <param name="filename">The filename.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
/// <exception cref="ArgumentNullException">The filename is null.</exception>
|
/// <exception cref="ArgumentNullException">The filename is null.</exception>
|
||||||
public virtual string GetValidFilename(string filename)
|
public string GetValidFilename(string filename)
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder(filename);
|
var invalid = Path.GetInvalidFileNameChars();
|
||||||
|
var first = filename.IndexOfAny(invalid);
|
||||||
foreach (var c in Path.GetInvalidFileNameChars())
|
if (first == -1)
|
||||||
{
|
{
|
||||||
builder = builder.Replace(c, ' ');
|
// Fast path for clean strings
|
||||||
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.ToString();
|
return string.Create(
|
||||||
|
filename.Length,
|
||||||
|
(filename, invalid, first),
|
||||||
|
(chars, state) =>
|
||||||
|
{
|
||||||
|
state.filename.AsSpan().CopyTo(chars);
|
||||||
|
|
||||||
|
chars[state.first++] = ' ';
|
||||||
|
|
||||||
|
var len = chars.Length;
|
||||||
|
foreach (var c in state.invalid)
|
||||||
|
{
|
||||||
|
for (int i = state.first; i < len; i++)
|
||||||
|
{
|
||||||
|
if (chars[i] == c)
|
||||||
|
{
|
||||||
|
chars[i] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -585,7 +601,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
return GetFiles(path, null, false, recursive);
|
return GetFiles(path, null, false, recursive);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string> extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
|
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
|
||||||
{
|
{
|
||||||
var enumerationOptions = GetEnumerationOptions(recursive);
|
var enumerationOptions = GetEnumerationOptions(recursive);
|
||||||
|
|
||||||
@ -639,7 +655,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
return GetFilePaths(path, null, false, recursive);
|
return GetFilePaths(path, null, false, recursive);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
|
public virtual IEnumerable<string> GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
|
||||||
{
|
{
|
||||||
var enumerationOptions = GetEnumerationOptions(recursive);
|
var enumerationOptions = GetEnumerationOptions(recursive);
|
||||||
|
|
||||||
@ -684,20 +700,5 @@ namespace Emby.Server.Implementations.IO
|
|||||||
AttributesToSkip = 0
|
AttributesToSkip = 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RunProcess(string path, string args, string workingDirectory)
|
|
||||||
{
|
|
||||||
using (var process = Process.Start(new ProcessStartInfo
|
|
||||||
{
|
|
||||||
Arguments = args,
|
|
||||||
FileName = path,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
WorkingDirectory = workingDirectory,
|
|
||||||
WindowStyle = ProcessWindowStyle.Normal
|
|
||||||
}))
|
|
||||||
{
|
|
||||||
process.WaitForExit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
|
|
||||||
public string Extension => ".mblink";
|
public string Extension => ".mblink";
|
||||||
|
|
||||||
public string Resolve(string shortcutPath)
|
public string? Resolve(string shortcutPath)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(shortcutPath))
|
if (string.IsNullOrEmpty(shortcutPath))
|
||||||
{
|
{
|
||||||
|
@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
{
|
{
|
||||||
public class StreamHelper : IStreamHelper
|
public class StreamHelper : IStreamHelper
|
||||||
{
|
{
|
||||||
public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken)
|
public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action? onStarted, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||||
try
|
try
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations
|
namespace Emby.Server.Implementations
|
||||||
{
|
{
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -191,7 +193,7 @@ namespace Emby.Server.Implementations.Images
|
|||||||
InputPaths = GetStripCollageImagePaths(primaryItem, items).ToArray()
|
InputPaths = GetStripCollageImagePaths(primaryItem, items).ToArray()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (options.InputPaths.Length == 0)
|
if (options.InputPaths.Count == 0)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -29,9 +31,7 @@ namespace Emby.Server.Implementations.Images
|
|||||||
{
|
{
|
||||||
var subItem = i.Item2;
|
var subItem = i.Item2;
|
||||||
|
|
||||||
var episode = subItem as Episode;
|
if (subItem is Episode episode)
|
||||||
|
|
||||||
if (episode != null)
|
|
||||||
{
|
{
|
||||||
var series = episode.Series;
|
var series = episode.Series;
|
||||||
if (series != null && series.HasImage(ImageType.Primary))
|
if (series != null && series.HasImage(ImageType.Primary))
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable enable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using DotNet.Globbing;
|
using DotNet.Globbing;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -48,6 +50,7 @@ using MediaBrowser.Providers.MediaInfo;
|
|||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||||
|
using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
|
||||||
using Genre = MediaBrowser.Controller.Entities.Genre;
|
using Genre = MediaBrowser.Controller.Entities.Genre;
|
||||||
using Person = MediaBrowser.Controller.Entities.Person;
|
using Person = MediaBrowser.Controller.Entities.Person;
|
||||||
using VideoResolver = Emby.Naming.Video.VideoResolver;
|
using VideoResolver = Emby.Naming.Video.VideoResolver;
|
||||||
@ -175,10 +178,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
lock (_rootFolderSyncLock)
|
lock (_rootFolderSyncLock)
|
||||||
{
|
{
|
||||||
if (_rootFolder == null)
|
_rootFolder ??= CreateRootFolder();
|
||||||
{
|
|
||||||
_rootFolder = CreateRootFolder();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -558,7 +558,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService)
|
var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService)
|
||||||
{
|
{
|
||||||
Parent = parent,
|
Parent = parent,
|
||||||
Path = fullPath,
|
|
||||||
FileInfo = fileInfo,
|
FileInfo = fileInfo,
|
||||||
CollectionType = collectionType,
|
CollectionType = collectionType,
|
||||||
LibraryOptions = libraryOptions
|
LibraryOptions = libraryOptions
|
||||||
@ -684,7 +683,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
ResolverHelper.SetInitialItemValues(item, parent, _fileSystem, this, directoryService);
|
ResolverHelper.SetInitialItemValues(item, parent, this, directoryService);
|
||||||
}
|
}
|
||||||
|
|
||||||
items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions));
|
items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions));
|
||||||
@ -1163,7 +1162,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
progress.Report(percent * 100);
|
progress.Report(percent * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
_itemRepository.UpdateInheritedValues(cancellationToken);
|
_itemRepository.UpdateInheritedValues();
|
||||||
|
|
||||||
progress.Report(100);
|
progress.Report(100);
|
||||||
}
|
}
|
||||||
@ -2517,7 +2516,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
|
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
|
||||||
{
|
{
|
||||||
var series = episode.Series;
|
var series = episode.Series;
|
||||||
bool? isAbsoluteNaming = series == null ? false : string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase);
|
bool? isAbsoluteNaming = series != null && string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase);
|
||||||
if (!isAbsoluteNaming.Value)
|
if (!isAbsoluteNaming.Value)
|
||||||
{
|
{
|
||||||
// In other words, no filter applied
|
// In other words, no filter applied
|
||||||
@ -2529,9 +2528,23 @@ namespace Emby.Server.Implementations.Library
|
|||||||
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
|
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
|
||||||
|
|
||||||
// TODO nullable - what are we trying to do there with empty episodeInfo?
|
// TODO nullable - what are we trying to do there with empty episodeInfo?
|
||||||
var episodeInfo = episode.IsFileProtocol
|
EpisodeInfo episodeInfo = null;
|
||||||
? resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new Naming.TV.EpisodeInfo(episode.Path)
|
if (episode.IsFileProtocol)
|
||||||
: new Naming.TV.EpisodeInfo(episode.Path);
|
{
|
||||||
|
episodeInfo = resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming);
|
||||||
|
// Resolve from parent folder if it's not the Season folder
|
||||||
|
if (episodeInfo == null && episode.Parent.GetType() == typeof(Folder))
|
||||||
|
{
|
||||||
|
episodeInfo = resolver.Resolve(episode.Parent.Path, true, null, null, isAbsoluteNaming);
|
||||||
|
if (episodeInfo != null)
|
||||||
|
{
|
||||||
|
// add the container
|
||||||
|
episodeInfo.Container = Path.GetExtension(episode.Path)?.TrimStart('.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
episodeInfo ??= new EpisodeInfo(episode.Path);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -2880,6 +2893,12 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void UpdatePeople(BaseItem item, List<PersonInfo> people)
|
public void UpdatePeople(BaseItem item, List<PersonInfo> people)
|
||||||
|
{
|
||||||
|
UpdatePeopleAsync(item, people, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task UpdatePeopleAsync(BaseItem item, List<PersonInfo> people, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!item.SupportsPeople)
|
if (!item.SupportsPeople)
|
||||||
{
|
{
|
||||||
@ -2887,6 +2906,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
_itemRepository.UpdatePeople(item.Id, people);
|
_itemRepository.UpdatePeople(item.Id, people);
|
||||||
|
|
||||||
|
await SavePeopleMetadataAsync(people, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex)
|
public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex)
|
||||||
@ -2990,6 +3011,58 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var personsToSave = new List<BaseItem>();
|
||||||
|
|
||||||
|
foreach (var person in people)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var itemUpdateType = ItemUpdateType.MetadataDownload;
|
||||||
|
var saveEntity = false;
|
||||||
|
var personEntity = GetPerson(person.Name);
|
||||||
|
|
||||||
|
// if PresentationUniqueKey is empty it's likely a new item.
|
||||||
|
if (string.IsNullOrEmpty(personEntity.PresentationUniqueKey))
|
||||||
|
{
|
||||||
|
personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey();
|
||||||
|
saveEntity = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var id in person.ProviderIds)
|
||||||
|
{
|
||||||
|
if (!string.Equals(personEntity.GetProviderId(id.Key), id.Value, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
personEntity.SetProviderId(id.Key, id.Value);
|
||||||
|
saveEntity = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary))
|
||||||
|
{
|
||||||
|
personEntity.SetImage(
|
||||||
|
new ItemImageInfo
|
||||||
|
{
|
||||||
|
Path = person.ImageUrl,
|
||||||
|
Type = ImageType.Primary
|
||||||
|
},
|
||||||
|
0);
|
||||||
|
|
||||||
|
saveEntity = true;
|
||||||
|
itemUpdateType = ItemUpdateType.ImageUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saveEntity)
|
||||||
|
{
|
||||||
|
personsToSave.Add(personEntity);
|
||||||
|
await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateItems(personsToSave, null, CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
private void StartScanInBackground()
|
private void StartScanInBackground()
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -590,18 +592,9 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
public Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken)
|
public Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var info = _openStreams.Values.FirstOrDefault(i =>
|
var info = _openStreams.FirstOrDefault(i => i.Value != null && string.Equals(i.Value.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase));
|
||||||
{
|
|
||||||
var liveStream = i as ILiveStream;
|
|
||||||
if (liveStream != null)
|
|
||||||
{
|
|
||||||
return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return Task.FromResult(info.Value as IDirectStreamProvider);
|
||||||
});
|
|
||||||
|
|
||||||
return Task.FromResult(info as IDirectStreamProvider);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken)
|
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken)
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -100,8 +102,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
|
public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
|
||||||
{
|
{
|
||||||
var genre = item as MusicGenre;
|
if (item is MusicGenre genre)
|
||||||
if (genre != null)
|
|
||||||
{
|
{
|
||||||
return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
|
return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable enable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using MediaBrowser.Common.Providers;
|
using MediaBrowser.Common.Providers;
|
||||||
@ -96,8 +94,14 @@ namespace Emby.Server.Implementations.Library
|
|||||||
// We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results
|
// We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results
|
||||||
// when the sub path matches a similar but in-complete subpath
|
// when the sub path matches a similar but in-complete subpath
|
||||||
var oldSubPathEndsWithSeparator = subPath[^1] == newDirectorySeparatorChar;
|
var oldSubPathEndsWithSeparator = subPath[^1] == newDirectorySeparatorChar;
|
||||||
if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase)
|
if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase))
|
||||||
|| (!oldSubPathEndsWithSeparator && path[subPath.Length] != newDirectorySeparatorChar))
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.Length > subPath.Length
|
||||||
|
&& !oldSubPathEndsWithSeparator
|
||||||
|
&& path[subPath.Length] != newDirectorySeparatorChar)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,10 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="item">The item.</param>
|
/// <param name="item">The item.</param>
|
||||||
/// <param name="parent">The parent.</param>
|
/// <param name="parent">The parent.</param>
|
||||||
/// <param name="fileSystem">The file system.</param>
|
|
||||||
/// <param name="libraryManager">The library manager.</param>
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
/// <param name="directoryService">The directory service.</param>
|
/// <param name="directoryService">The directory service.</param>
|
||||||
/// <exception cref="ArgumentException">Item must have a path</exception>
|
/// <exception cref="ArgumentException">Item must have a path.</exception>
|
||||||
public static void SetInitialItemValues(BaseItem item, Folder parent, IFileSystem fileSystem, ILibraryManager libraryManager, IDirectoryService directoryService)
|
public static void SetInitialItemValues(BaseItem item, Folder? parent, ILibraryManager libraryManager, IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
// This version of the below method has no ItemResolveArgs, so we have to require the path already being set
|
// This version of the below method has no ItemResolveArgs, so we have to require the path already being set
|
||||||
if (string.IsNullOrEmpty(item.Path))
|
if (string.IsNullOrEmpty(item.Path))
|
||||||
@ -43,9 +42,14 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
// Make sure DateCreated and DateModified have values
|
// Make sure DateCreated and DateModified have values
|
||||||
var fileInfo = directoryService.GetFile(item.Path);
|
var fileInfo = directoryService.GetFile(item.Path);
|
||||||
SetDateCreated(item, fileSystem, fileInfo);
|
if (fileInfo == null)
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException("Can't find item path.", item.Path);
|
||||||
|
}
|
||||||
|
|
||||||
EnsureName(item, item.Path, fileInfo);
|
SetDateCreated(item, fileInfo);
|
||||||
|
|
||||||
|
EnsureName(item, fileInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -72,9 +76,9 @@ namespace Emby.Server.Implementations.Library
|
|||||||
item.Id = libraryManager.GetNewItemId(item.Path, item.GetType());
|
item.Id = libraryManager.GetNewItemId(item.Path, item.GetType());
|
||||||
|
|
||||||
// Make sure the item has a name
|
// Make sure the item has a name
|
||||||
EnsureName(item, item.Path, args.FileInfo);
|
EnsureName(item, args.FileInfo);
|
||||||
|
|
||||||
item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 ||
|
item.IsLocked = item.Path.Contains("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) ||
|
||||||
item.GetParents().Any(i => i.IsLocked);
|
item.GetParents().Any(i => i.IsLocked);
|
||||||
|
|
||||||
// Make sure DateCreated and DateModified have values
|
// Make sure DateCreated and DateModified have values
|
||||||
@ -84,28 +88,15 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ensures the name.
|
/// Ensures the name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void EnsureName(BaseItem item, string fullPath, FileSystemMetadata fileInfo)
|
private static void EnsureName(BaseItem item, FileSystemMetadata fileInfo)
|
||||||
{
|
{
|
||||||
// If the subclass didn't supply a name, add it here
|
// If the subclass didn't supply a name, add it here
|
||||||
if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(fullPath))
|
if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(item.Path))
|
||||||
{
|
{
|
||||||
var fileName = fileInfo == null ? Path.GetFileName(fullPath) : fileInfo.Name;
|
item.Name = fileInfo.IsDirectory ? fileInfo.Name : Path.GetFileNameWithoutExtension(fileInfo.Name);
|
||||||
|
|
||||||
item.Name = GetDisplayName(fileName, fileInfo != null && fileInfo.IsDirectory);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the display name.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path.</param>
|
|
||||||
/// <param name="isDirectory">if set to <c>true</c> [is directory].</param>
|
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
private static string GetDisplayName(string path, bool isDirectory)
|
|
||||||
{
|
|
||||||
return isDirectory ? Path.GetFileName(path) : Path.GetFileNameWithoutExtension(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ensures DateCreated and DateModified have values.
|
/// Ensures DateCreated and DateModified have values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -114,21 +105,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// <param name="args">The args.</param>
|
/// <param name="args">The args.</param>
|
||||||
private static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args)
|
private static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args)
|
||||||
{
|
{
|
||||||
if (fileSystem == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(fileSystem));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(args));
|
|
||||||
}
|
|
||||||
|
|
||||||
// See if a different path came out of the resolver than what went in
|
// See if a different path came out of the resolver than what went in
|
||||||
if (!fileSystem.AreEqual(args.Path, item.Path))
|
if (!fileSystem.AreEqual(args.Path, item.Path))
|
||||||
{
|
{
|
||||||
@ -136,7 +112,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (childData != null)
|
if (childData != null)
|
||||||
{
|
{
|
||||||
SetDateCreated(item, fileSystem, childData);
|
SetDateCreated(item, childData);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -144,17 +120,17 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (fileData.Exists)
|
if (fileData.Exists)
|
||||||
{
|
{
|
||||||
SetDateCreated(item, fileSystem, fileData);
|
SetDateCreated(item, fileData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SetDateCreated(item, fileSystem, args.FileInfo);
|
SetDateCreated(item, args.FileInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetDateCreated(BaseItem item, IFileSystem fileSystem, FileSystemMetadata info)
|
private static void SetDateCreated(BaseItem item, FileSystemMetadata? info)
|
||||||
{
|
{
|
||||||
var config = BaseItem.ConfigurationManager.GetMetadataConfiguration();
|
var config = BaseItem.ConfigurationManager.GetMetadataConfiguration();
|
||||||
|
|
||||||
@ -163,7 +139,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
// directoryService.getFile may return null
|
// directoryService.getFile may return null
|
||||||
if (info != null)
|
if (info != null)
|
||||||
{
|
{
|
||||||
var dateCreated = fileSystem.GetCreationTimeUtc(info);
|
var dateCreated = info.CreationTimeUtc;
|
||||||
|
|
||||||
if (dateCreated.Equals(DateTime.MinValue))
|
if (dateCreated.Equals(DateTime.MinValue))
|
||||||
{
|
{
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -165,13 +167,13 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||||||
|
|
||||||
protected void SetVideoType(Video video, VideoFileInfo videoInfo)
|
protected void SetVideoType(Video video, VideoFileInfo videoInfo)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(video.Path);
|
var extension = Path.GetExtension(video.Path.AsSpan());
|
||||||
video.VideoType = string.Equals(extension, ".iso", StringComparison.OrdinalIgnoreCase) ||
|
video.VideoType = extension.Equals(".iso", StringComparison.OrdinalIgnoreCase)
|
||||||
string.Equals(extension, ".img", StringComparison.OrdinalIgnoreCase) ?
|
|| extension.Equals(".img", StringComparison.OrdinalIgnoreCase)
|
||||||
VideoType.Iso :
|
? VideoType.Iso
|
||||||
VideoType.VideoFile;
|
: VideoType.VideoFile;
|
||||||
|
|
||||||
video.IsShortcut = string.Equals(extension, ".strm", StringComparison.OrdinalIgnoreCase);
|
video.IsShortcut = extension.Equals(".strm", StringComparison.OrdinalIgnoreCase);
|
||||||
video.IsPlaceHolder = videoInfo.IsStub;
|
video.IsPlaceHolder = videoInfo.IsStub;
|
||||||
|
|
||||||
if (videoInfo.IsStub)
|
if (videoInfo.IsStub)
|
||||||
@ -193,11 +195,11 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||||||
{
|
{
|
||||||
if (video.VideoType == VideoType.Iso)
|
if (video.VideoType == VideoType.Iso)
|
||||||
{
|
{
|
||||||
if (video.Path.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1)
|
if (video.Path.Contains("dvd", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
video.IsoType = IsoType.Dvd;
|
video.IsoType = IsoType.Dvd;
|
||||||
}
|
}
|
||||||
else if (video.Path.IndexOf("bluray", StringComparison.OrdinalIgnoreCase) != -1)
|
else if (video.Path.Contains("bluray", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
video.IsoType = IsoType.BluRay;
|
video.IsoType = IsoType.BluRay;
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user