mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-12-17 14:33:06 +03:00
Compare commits
29 Commits
only-count
...
explicit-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45d51568e7 | ||
|
|
c370de77de | ||
|
|
f6709a69e7 | ||
|
|
4cdd8c8233 | ||
|
|
6e60634c9f | ||
|
|
12c5d6b636 | ||
|
|
b617c62f8e | ||
|
|
035b5895b0 | ||
|
|
22da5187c8 | ||
|
|
5804d6840c | ||
|
|
b50ce1ad6b | ||
|
|
481ee03f35 | ||
|
|
d91adb5d54 | ||
|
|
ef7f138a4e | ||
|
|
2e8d9a311b | ||
|
|
4c5a3fbff3 | ||
|
|
636908fc4d | ||
|
|
997362fc97 | ||
|
|
c5147341e3 | ||
|
|
ca33bcebf0 | ||
|
|
d32f487e8e | ||
|
|
fb65f8f853 | ||
|
|
2a0b90e385 | ||
|
|
dde70fd8a2 | ||
|
|
98d1d0cb35 | ||
|
|
ba76a8f3ad | ||
|
|
8cd5652157 | ||
|
|
8aff4227d9 | ||
|
|
026f7472cb |
@@ -3,7 +3,7 @@
|
|||||||
"isRoot": true,
|
"isRoot": true,
|
||||||
"tools": {
|
"tools": {
|
||||||
"dotnet-ef": {
|
"dotnet-ef": {
|
||||||
"version": "9.0.10",
|
"version": "9.0.11",
|
||||||
"commands": [
|
"commands": [
|
||||||
"dotnet-ef"
|
"dotnet-ef"
|
||||||
]
|
]
|
||||||
|
|||||||
10
.github/workflows/ci-codeql-analysis.yml
vendored
10
.github/workflows/ci-codeql-analysis.yml
vendored
@@ -20,18 +20,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
||||||
with:
|
with:
|
||||||
dotnet-version: '9.0.x'
|
dotnet-version: '9.0.x'
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
queries: +security-extended
|
queries: +security-extended
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
uses: github/codeql-action/autobuild@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
uses: github/codeql-action/analyze@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6
|
||||||
|
|||||||
16
.github/workflows/ci-compat.yml
vendored
16
.github/workflows/ci-compat.yml
vendored
@@ -11,13 +11,13 @@ jobs:
|
|||||||
permissions: read-all
|
permissions: read-all
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
|
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
||||||
with:
|
with:
|
||||||
dotnet-version: '9.0.x'
|
dotnet-version: '9.0.x'
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
dotnet build Jellyfin.Server -o ./out
|
dotnet build Jellyfin.Server -o ./out
|
||||||
|
|
||||||
- name: Upload Head
|
- name: Upload Head
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: abi-head
|
name: abi-head
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
@@ -40,14 +40,14 @@ jobs:
|
|||||||
permissions: read-all
|
permissions: read-all
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
||||||
with:
|
with:
|
||||||
dotnet-version: '9.0.x'
|
dotnet-version: '9.0.x'
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
dotnet build Jellyfin.Server -o ./out
|
dotnet build Jellyfin.Server -o ./out
|
||||||
|
|
||||||
- name: Upload Head
|
- name: Upload Head
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: abi-base
|
name: abi-base
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
@@ -85,13 +85,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download abi-head
|
- name: Download abi-head
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: abi-head
|
name: abi-head
|
||||||
path: abi-head
|
path: abi-head
|
||||||
|
|
||||||
- name: Download abi-base
|
- name: Download abi-base
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: abi-base
|
name: abi-base
|
||||||
path: abi-base
|
path: abi-base
|
||||||
|
|||||||
24
.github/workflows/ci-openapi.yml
vendored
24
.github/workflows/ci-openapi.yml
vendored
@@ -16,18 +16,18 @@ jobs:
|
|||||||
permissions: read-all
|
permissions: read-all
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
||||||
with:
|
with:
|
||||||
dotnet-version: '9.0.x'
|
dotnet-version: '9.0.x'
|
||||||
- name: Generate openapi.json
|
- name: Generate openapi.json
|
||||||
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||||
- name: Upload openapi.json
|
- name: Upload openapi.json
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: openapi-head
|
name: openapi-head
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
@@ -41,7 +41,7 @@ jobs:
|
|||||||
permissions: read-all
|
permissions: read-all
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
@@ -55,13 +55,13 @@ jobs:
|
|||||||
ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/$HEAD_REF)
|
ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/$HEAD_REF)
|
||||||
git checkout --progress --force $ANCESTOR_REF
|
git checkout --progress --force $ANCESTOR_REF
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
||||||
with:
|
with:
|
||||||
dotnet-version: '9.0.x'
|
dotnet-version: '9.0.x'
|
||||||
- name: Generate openapi.json
|
- name: Generate openapi.json
|
||||||
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||||
- name: Upload openapi.json
|
- name: Upload openapi.json
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: openapi-base
|
name: openapi-base
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
@@ -80,12 +80,12 @@ jobs:
|
|||||||
- openapi-base
|
- openapi-base
|
||||||
steps:
|
steps:
|
||||||
- name: Download openapi-head
|
- name: Download openapi-head
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: openapi-head
|
name: openapi-head
|
||||||
path: openapi-head
|
path: openapi-head
|
||||||
- name: Download openapi-base
|
- name: Download openapi-base
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: openapi-base
|
name: openapi-base
|
||||||
path: openapi-base
|
path: openapi-base
|
||||||
@@ -158,7 +158,7 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
|
echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
|
||||||
- name: Download openapi-head
|
- name: Download openapi-head
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: openapi-head
|
name: openapi-head
|
||||||
path: openapi-head
|
path: openapi-head
|
||||||
@@ -172,7 +172,7 @@ jobs:
|
|||||||
strip_components: 1
|
strip_components: 1
|
||||||
target: "/srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
|
target: "/srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
|
||||||
- name: Move openapi.json (unstable) into place
|
- name: Move openapi.json (unstable) into place
|
||||||
uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2
|
uses: appleboy/ssh-action@823bd89e131d8d508129f9443cad5855e9ba96f0 # v1.2.4
|
||||||
with:
|
with:
|
||||||
host: "${{ secrets.REPO_HOST }}"
|
host: "${{ secrets.REPO_HOST }}"
|
||||||
username: "${{ secrets.REPO_USER }}"
|
username: "${{ secrets.REPO_USER }}"
|
||||||
@@ -220,7 +220,7 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||||
- name: Download openapi-head
|
- name: Download openapi-head
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: openapi-head
|
name: openapi-head
|
||||||
path: openapi-head
|
path: openapi-head
|
||||||
@@ -234,7 +234,7 @@ jobs:
|
|||||||
strip_components: 1
|
strip_components: 1
|
||||||
target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
|
target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
|
||||||
- name: Move openapi.json (stable) into place
|
- name: Move openapi.json (stable) into place
|
||||||
uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2
|
uses: appleboy/ssh-action@823bd89e131d8d508129f9443cad5855e9ba96f0 # v1.2.4
|
||||||
with:
|
with:
|
||||||
host: "${{ secrets.REPO_HOST }}"
|
host: "${{ secrets.REPO_HOST }}"
|
||||||
username: "${{ secrets.REPO_USER }}"
|
username: "${{ secrets.REPO_USER }}"
|
||||||
|
|||||||
6
.github/workflows/ci-tests.yml
vendored
6
.github/workflows/ci-tests.yml
vendored
@@ -20,9 +20,9 @@ jobs:
|
|||||||
|
|
||||||
runs-on: "${{ matrix.os }}"
|
runs-on: "${{ matrix.os }}"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
|
|
||||||
- uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
- uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
||||||
with:
|
with:
|
||||||
dotnet-version: ${{ env.SDK_VERSION }}
|
dotnet-version: ${{ env.SDK_VERSION }}
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
--verbosity minimal
|
--verbosity minimal
|
||||||
|
|
||||||
- name: Merge code coverage results
|
- name: Merge code coverage results
|
||||||
uses: danielpalme/ReportGenerator-GitHub-Action@9870ed167742d546b99962ff815fcc1098355ed8 # v5.4.17
|
uses: danielpalme/ReportGenerator-GitHub-Action@ee0ae774f6d3afedcbd1683c1ab21b83670bdf8e # v5.5.1
|
||||||
with:
|
with:
|
||||||
reports: "**/coverage.cobertura.xml"
|
reports: "**/coverage.cobertura.xml"
|
||||||
targetdir: "merged/"
|
targetdir: "merged/"
|
||||||
|
|||||||
6
.github/workflows/commands.yml
vendored
6
.github/workflows/commands.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
reactions: '+1'
|
reactions: '+1'
|
||||||
|
|
||||||
- name: Checkout the latest code
|
- name: Checkout the latest code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -40,11 +40,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: pull in script
|
- name: pull in script
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
repository: jellyfin/jellyfin-triage-script
|
repository: jellyfin/jellyfin-triage-script
|
||||||
- name: install python
|
- name: install python
|
||||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: '3.14'
|
python-version: '3.14'
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
|
|||||||
2
.github/workflows/issue-stale.yml
vendored
2
.github/workflows/issue-stale.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ contains(github.repository, 'jellyfin/') }}
|
if: ${{ contains(github.repository, 'jellyfin/') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
|
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
ascending: true
|
ascending: true
|
||||||
|
|||||||
4
.github/workflows/issue-template-check.yml
vendored
4
.github/workflows/issue-template-check.yml
vendored
@@ -10,11 +10,11 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- name: pull in script
|
- name: pull in script
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
repository: jellyfin/jellyfin-triage-script
|
repository: jellyfin/jellyfin-triage-script
|
||||||
- name: install python
|
- name: install python
|
||||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||||
with:
|
with:
|
||||||
python-version: '3.14'
|
python-version: '3.14'
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
|
|||||||
2
.github/workflows/pull-request-stale.yaml
vendored
2
.github/workflows/pull-request-stale.yaml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ contains(github.repository, 'jellyfin/') }}
|
if: ${{ contains(github.repository, 'jellyfin/') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
|
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
ascending: true
|
ascending: true
|
||||||
|
|||||||
4
.github/workflows/release-bump-version.yaml
vendored
4
.github/workflows/release-bump-version.yaml
vendored
@@ -33,7 +33,7 @@ jobs:
|
|||||||
yq-version: v4.9.8
|
yq-version: v4.9.8
|
||||||
|
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ env.TAG_BRANCH }}
|
ref: ${{ env.TAG_BRANCH }}
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
NEXT_VERSION: ${{ github.event.inputs.NEXT_VERSION }}
|
NEXT_VERSION: ${{ github.event.inputs.NEXT_VERSION }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ env.TAG_BRANCH }}
|
ref: ${{ env.TAG_BRANCH }}
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,7 @@
|
|||||||
- [sachk](https://github.com/sachk)
|
- [sachk](https://github.com/sachk)
|
||||||
- [sammyrc34](https://github.com/sammyrc34)
|
- [sammyrc34](https://github.com/sammyrc34)
|
||||||
- [samuel9554](https://github.com/samuel9554)
|
- [samuel9554](https://github.com/samuel9554)
|
||||||
|
- [SapientGuardian](https://github.com/SapientGuardian)
|
||||||
- [scheidleon](https://github.com/scheidleon)
|
- [scheidleon](https://github.com/scheidleon)
|
||||||
- [sebPomme](https://github.com/sebPomme)
|
- [sebPomme](https://github.com/sebPomme)
|
||||||
- [SegiH](https://github.com/SegiH)
|
- [SegiH](https://github.com/SegiH)
|
||||||
@@ -205,6 +206,7 @@
|
|||||||
- [theshoeshiner](https://github.com/theshoeshiner)
|
- [theshoeshiner](https://github.com/theshoeshiner)
|
||||||
- [TokerX](https://github.com/TokerX)
|
- [TokerX](https://github.com/TokerX)
|
||||||
- [GeneMarks](https://github.com/GeneMarks)
|
- [GeneMarks](https://github.com/GeneMarks)
|
||||||
|
- [martenumberto](https://github.com/martenumberto)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
|
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
|
||||||
<ItemGroup Label="Package Dependencies">
|
<ItemGroup Label="Package Dependencies">
|
||||||
<PackageVersion Include="AsyncKeyedLock" Version="7.1.7" />
|
<PackageVersion Include="AsyncKeyedLock" Version="7.1.8" />
|
||||||
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
|
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
|
||||||
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
|
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
|
||||||
<PackageVersion Include="AutoFixture" Version="4.18.1" />
|
<PackageVersion Include="AutoFixture" Version="4.18.1" />
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<PackageVersion Include="Diacritics" Version="4.0.17" />
|
<PackageVersion Include="Diacritics" Version="4.0.17" />
|
||||||
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
|
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
|
||||||
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
|
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
|
||||||
<PackageVersion Include="FsCheck.Xunit" Version="3.3.1" />
|
<PackageVersion Include="FsCheck.Xunit" Version="3.3.2" />
|
||||||
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="8.3.1.1" />
|
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="8.3.1.1" />
|
||||||
<PackageVersion Include="ICU4N.Transliterator" Version="60.1.0-alpha.356" />
|
<PackageVersion Include="ICU4N.Transliterator" Version="60.1.0-alpha.356" />
|
||||||
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.8" />
|
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.8" />
|
||||||
@@ -26,33 +26,33 @@
|
|||||||
<PackageVersion Include="libse" Version="4.0.12" />
|
<PackageVersion Include="libse" Version="4.0.12" />
|
||||||
<PackageVersion Include="LrcParser" Version="2025.623.0" />
|
<PackageVersion Include="LrcParser" Version="2025.623.0" />
|
||||||
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" />
|
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="4.14.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="4.14.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
|
||||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.10" />
|
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.11" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||||
<PackageVersion Include="MimeTypes" Version="2.5.2" />
|
<PackageVersion Include="MimeTypes" Version="2.5.2" />
|
||||||
<PackageVersion Include="Morestachio" Version="5.0.1.631" />
|
<PackageVersion Include="Morestachio" Version="5.0.1.631" />
|
||||||
<PackageVersion Include="Moq" Version="4.18.4" />
|
<PackageVersion Include="Moq" Version="4.18.4" />
|
||||||
@@ -62,13 +62,13 @@
|
|||||||
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
||||||
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.1" />
|
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.1" />
|
||||||
<PackageVersion Include="prometheus-net" Version="8.2.1" />
|
<PackageVersion Include="prometheus-net" Version="8.2.1" />
|
||||||
<PackageVersion Include="Polly" Version="8.6.4" />
|
<PackageVersion Include="Polly" Version="8.6.5" />
|
||||||
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
|
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||||
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
||||||
<PackageVersion Include="Serilog.Expressions" Version="5.0.0" />
|
<PackageVersion Include="Serilog.Expressions" Version="5.0.0" />
|
||||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
<PackageVersion Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
||||||
<PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" />
|
<PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" />
|
||||||
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
|
<PackageVersion Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||||
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
|
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||||
<PackageVersion Include="Serilog.Sinks.Graylog" Version="3.1.1" />
|
<PackageVersion Include="Serilog.Sinks.Graylog" Version="3.1.1" />
|
||||||
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
|
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
|
||||||
@@ -84,11 +84,11 @@
|
|||||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||||
<PackageVersion Include="System.Globalization" Version="4.3.0" />
|
<PackageVersion Include="System.Globalization" Version="4.3.0" />
|
||||||
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />
|
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />
|
||||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.10" />
|
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.11" />
|
||||||
<PackageVersion Include="System.Text.Json" Version="9.0.10" />
|
<PackageVersion Include="System.Text.Json" Version="9.0.11" />
|
||||||
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.10" />
|
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.11" />
|
||||||
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
||||||
<PackageVersion Include="z440.atl.core" Version="7.6.0" />
|
<PackageVersion Include="z440.atl.core" Version="7.9.0" />
|
||||||
<PackageVersion Include="TMDbLib" Version="2.3.0" />
|
<PackageVersion Include="TMDbLib" Version="2.3.0" />
|
||||||
<PackageVersion Include="UTF.Unknown" Version="2.6.0" />
|
<PackageVersion Include="UTF.Unknown" Version="2.6.0" />
|
||||||
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
|
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Naming</PackageId>
|
<PackageId>Jellyfin.Naming</PackageId>
|
||||||
<VersionPrefix>10.11.3</VersionPrefix>
|
<VersionPrefix>10.11.4</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.IO;
|
using MediaBrowser.Controller.IO;
|
||||||
using MediaBrowser.Controller.Resolvers;
|
using MediaBrowser.Controller.Resolvers;
|
||||||
@@ -70,12 +71,55 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule
|
|||||||
{
|
{
|
||||||
// If file has content, base ignoring off the content .gitignore-style rules
|
// If file has content, base ignoring off the content .gitignore-style rules
|
||||||
var rules = ignoreFileContent.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
var rules = ignoreFileContent.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
return CheckIgnoreRules(path, rules, isDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether a path should be ignored based on an array of ignore rules.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path to check.</param>
|
||||||
|
/// <param name="rules">The array of ignore rules.</param>
|
||||||
|
/// <param name="isDirectory">Whether the path is a directory.</param>
|
||||||
|
/// <returns>True if the path should be ignored.</returns>
|
||||||
|
internal static bool CheckIgnoreRules(string path, string[] rules, bool isDirectory)
|
||||||
|
=> CheckIgnoreRules(path, rules, isDirectory, IsWindows);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether a path should be ignored based on an array of ignore rules.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path to check.</param>
|
||||||
|
/// <param name="rules">The array of ignore rules.</param>
|
||||||
|
/// <param name="isDirectory">Whether the path is a directory.</param>
|
||||||
|
/// <param name="normalizePath">Whether to normalize backslashes to forward slashes (for Windows paths).</param>
|
||||||
|
/// <returns>True if the path should be ignored.</returns>
|
||||||
|
internal static bool CheckIgnoreRules(string path, string[] rules, bool isDirectory, bool normalizePath)
|
||||||
|
{
|
||||||
var ignore = new Ignore.Ignore();
|
var ignore = new Ignore.Ignore();
|
||||||
ignore.Add(rules);
|
|
||||||
|
// Add each rule individually to catch and skip invalid patterns
|
||||||
|
var validRulesAdded = 0;
|
||||||
|
foreach (var rule in rules)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ignore.Add(rule);
|
||||||
|
validRulesAdded++;
|
||||||
|
}
|
||||||
|
catch (RegexParseException)
|
||||||
|
{
|
||||||
|
// Ignore invalid patterns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no valid rules were added, fall back to ignoring everything (like an empty .ignore file)
|
||||||
|
if (validRulesAdded == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Mitigate the problem of the Ignore library not handling Windows paths correctly.
|
// Mitigate the problem of the Ignore library not handling Windows paths correctly.
|
||||||
// See https://github.com/jellyfin/jellyfin/issues/15484
|
// See https://github.com/jellyfin/jellyfin/issues/15484
|
||||||
var pathToCheck = IsWindows ? path.NormalizePath('/') : path;
|
var pathToCheck = normalizePath ? path.NormalizePath('/') : path;
|
||||||
|
|
||||||
// Add trailing slash for directories to match "folder/"
|
// Add trailing slash for directories to match "folder/"
|
||||||
if (isDirectory)
|
if (isDirectory)
|
||||||
|
|||||||
@@ -1058,6 +1058,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
IncludeItemTypes = [BaseItemKind.MusicArtist],
|
IncludeItemTypes = [BaseItemKind.MusicArtist],
|
||||||
Name = name,
|
Name = name,
|
||||||
|
UseRawName = true,
|
||||||
DtoOptions = options
|
DtoOptions = options
|
||||||
}).Cast<MusicArtist>()
|
}).Cast<MusicArtist>()
|
||||||
.OrderBy(i => i.IsAccessedByName ? 1 : 0)
|
.OrderBy(i => i.IsAccessedByName ? 1 : 0)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ using MediaBrowser.Controller.Entities;
|
|||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
|
using MediaBrowser.Controller.IO;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Activity;
|
using MediaBrowser.Model.Activity;
|
||||||
@@ -700,7 +701,18 @@ public class LibraryController : BaseJellyfinApiController
|
|||||||
// Quotes are valid in linux. They'll possibly cause issues here.
|
// Quotes are valid in linux. They'll possibly cause issues here.
|
||||||
var filename = Path.GetFileName(item.Path)?.Replace("\"", string.Empty, StringComparison.Ordinal);
|
var filename = Path.GetFileName(item.Path)?.Replace("\"", string.Empty, StringComparison.Ordinal);
|
||||||
|
|
||||||
return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path), filename, true);
|
var filePath = item.Path;
|
||||||
|
if (item.IsFileProtocol)
|
||||||
|
{
|
||||||
|
// PhysicalFile does not work well with symlinks at the moment.
|
||||||
|
var resolved = FileSystemHelper.ResolveLinkTarget(filePath, returnFinalTarget: true);
|
||||||
|
if (resolved is not null && resolved.Exists)
|
||||||
|
{
|
||||||
|
filePath = resolved.FullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PhysicalFile(filePath, MimeTypes.GetMimeType(filePath), filename, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public class TrickplayController : BaseJellyfinApiController
|
|||||||
[FromRoute, Required] int index,
|
[FromRoute, Required] int index,
|
||||||
[FromQuery] Guid? mediaSourceId)
|
[FromQuery] Guid? mediaSourceId)
|
||||||
{
|
{
|
||||||
var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
|
var item = _libraryManager.GetItemById<BaseItem>(mediaSourceId ?? itemId, User.GetUserId());
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|||||||
@@ -159,6 +159,13 @@ public static class StreamingHelpers
|
|||||||
|
|
||||||
string? containerInternal = Path.GetExtension(state.RequestedUrl);
|
string? containerInternal = Path.GetExtension(state.RequestedUrl);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(containerInternal)
|
||||||
|
&& (!string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId)
|
||||||
|
|| (mediaSource != null && mediaSource.IsInfiniteStream)))
|
||||||
|
{
|
||||||
|
containerInternal = ".ts";
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(streamingRequest.Container))
|
if (!string.IsNullOrEmpty(streamingRequest.Container))
|
||||||
{
|
{
|
||||||
containerInternal = streamingRequest.Container;
|
containerInternal = streamingRequest.Container;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Data</PackageId>
|
<PackageId>Jellyfin.Data</PackageId>
|
||||||
<VersionPrefix>10.11.3</VersionPrefix>
|
<VersionPrefix>10.11.4</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -275,9 +275,8 @@ public sealed class BaseItemRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
dbQuery = ApplyQueryPaging(dbQuery, filter);
|
dbQuery = ApplyQueryPaging(dbQuery, filter);
|
||||||
dbQuery = ApplyNavigations(dbQuery, filter);
|
|
||||||
|
|
||||||
result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
|
result.Items = GetEntities(dbQuery, context, filter).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
|
||||||
result.StartIndex = filter.StartIndex ?? 0;
|
result.StartIndex = filter.StartIndex ?? 0;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -295,9 +294,8 @@ public sealed class BaseItemRepository
|
|||||||
|
|
||||||
dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
|
dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
|
||||||
dbQuery = ApplyQueryPaging(dbQuery, filter);
|
dbQuery = ApplyQueryPaging(dbQuery, filter);
|
||||||
dbQuery = ApplyNavigations(dbQuery, filter);
|
|
||||||
|
|
||||||
return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
|
return GetEntities(dbQuery, context, filter).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -339,9 +337,7 @@ public sealed class BaseItemRepository
|
|||||||
mainquery = ApplyGroupingFilter(context, mainquery, filter);
|
mainquery = ApplyGroupingFilter(context, mainquery, filter);
|
||||||
mainquery = ApplyQueryPaging(mainquery, filter);
|
mainquery = ApplyQueryPaging(mainquery, filter);
|
||||||
|
|
||||||
mainquery = ApplyNavigations(mainquery, filter);
|
return GetEntities(mainquery, context, filter).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
|
||||||
|
|
||||||
return mainquery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -408,21 +404,6 @@ public sealed class BaseItemRepository
|
|||||||
return dbQuery;
|
return dbQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IQueryable<BaseItemEntity> ApplyNavigations(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter)
|
|
||||||
{
|
|
||||||
dbQuery = dbQuery.Include(e => e.TrailerTypes)
|
|
||||||
.Include(e => e.Provider)
|
|
||||||
.Include(e => e.LockedFields)
|
|
||||||
.Include(e => e.UserData);
|
|
||||||
|
|
||||||
if (filter.DtoOptions.EnableImages)
|
|
||||||
{
|
|
||||||
dbQuery = dbQuery.Include(e => e.Images);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dbQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IQueryable<BaseItemEntity> ApplyQueryPaging(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter)
|
private IQueryable<BaseItemEntity> ApplyQueryPaging(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter)
|
||||||
{
|
{
|
||||||
if (filter.Limit.HasValue || filter.StartIndex.HasValue)
|
if (filter.Limit.HasValue || filter.StartIndex.HasValue)
|
||||||
@@ -448,7 +429,6 @@ public sealed class BaseItemRepository
|
|||||||
dbQuery = TranslateQuery(dbQuery, context, filter);
|
dbQuery = TranslateQuery(dbQuery, context, filter);
|
||||||
dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
|
dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
|
||||||
dbQuery = ApplyQueryPaging(dbQuery, filter);
|
dbQuery = ApplyQueryPaging(dbQuery, filter);
|
||||||
dbQuery = ApplyNavigations(dbQuery, filter);
|
|
||||||
return dbQuery;
|
return dbQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,6 +440,79 @@ public sealed class BaseItemRepository
|
|||||||
return dbQuery;
|
return dbQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<BaseItemEntity> GetEntities(IQueryable<BaseItemEntity> dbQuery, JellyfinDbContext context, InternalItemsQuery filter)
|
||||||
|
{
|
||||||
|
var items = dbQuery.Where(e => e != null).ToDictionary(e => e.Id, e => e);
|
||||||
|
var itemIds = items.Keys.ToArray();
|
||||||
|
|
||||||
|
if (itemIds.Length == 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.TrailerTypes.Length > 0 || filter.IncludeItemTypes.Contains(BaseItemKind.Trailer))
|
||||||
|
{
|
||||||
|
var values = context.BaseItemTrailerTypes.WhereOneOrMany(itemIds, e => e.ItemId).GroupBy(x => x.ItemId).ToArray();
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
if (items.TryGetValue(value.Key, out var item))
|
||||||
|
{
|
||||||
|
item.TrailerTypes = value.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.DtoOptions.ContainsField(ItemFields.ProviderIds))
|
||||||
|
{
|
||||||
|
var values = context.BaseItemProviders.WhereOneOrMany(itemIds, e => e.ItemId).GroupBy(x => x.ItemId).ToArray();
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
if (items.TryGetValue(value.Key, out var item))
|
||||||
|
{
|
||||||
|
item.Provider = value.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.DtoOptions.ContainsField(ItemFields.Settings))
|
||||||
|
{
|
||||||
|
var values = context.BaseItemMetadataFields.WhereOneOrMany(itemIds, e => e.ItemId).GroupBy(x => x.ItemId).ToArray();
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
if (items.TryGetValue(value.Key, out var item))
|
||||||
|
{
|
||||||
|
item.LockedFields = value.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.DtoOptions.EnableImages)
|
||||||
|
{
|
||||||
|
var values = context.BaseItemImageInfos.WhereOneOrMany(itemIds, e => e.ItemId).GroupBy(x => x.ItemId).ToArray();
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
if (items.TryGetValue(value.Key, out var item))
|
||||||
|
{
|
||||||
|
item.Images = value.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.DtoOptions.EnableUserData)
|
||||||
|
{
|
||||||
|
var values = context.UserData.WhereOneOrMany(itemIds, e => e.ItemId).GroupBy(x => x.ItemId).ToArray();
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
if (items.TryGetValue(value.Key, out var item))
|
||||||
|
{
|
||||||
|
item.UserData = value.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items.Values.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int GetCount(InternalItemsQuery filter)
|
public int GetCount(InternalItemsQuery filter)
|
||||||
{
|
{
|
||||||
@@ -1529,14 +1582,14 @@ public sealed class BaseItemRepository
|
|||||||
|
|
||||||
private IQueryable<BaseItemEntity> ApplyOrder(IQueryable<BaseItemEntity> query, InternalItemsQuery filter, JellyfinDbContext context)
|
private IQueryable<BaseItemEntity> ApplyOrder(IQueryable<BaseItemEntity> query, InternalItemsQuery filter, JellyfinDbContext context)
|
||||||
{
|
{
|
||||||
var orderBy = filter.OrderBy;
|
var orderBy = filter.OrderBy.Where(e => e.OrderBy != ItemSortBy.Default).ToArray();
|
||||||
var hasSearch = !string.IsNullOrEmpty(filter.SearchTerm);
|
var hasSearch = !string.IsNullOrEmpty(filter.SearchTerm);
|
||||||
|
|
||||||
if (hasSearch)
|
if (hasSearch)
|
||||||
{
|
{
|
||||||
orderBy = filter.OrderBy = [(ItemSortBy.SortName, SortOrder.Ascending), .. orderBy];
|
orderBy = [(ItemSortBy.SortName, SortOrder.Ascending), .. orderBy];
|
||||||
}
|
}
|
||||||
else if (orderBy.Count == 0)
|
else if (orderBy.Length == 0)
|
||||||
{
|
{
|
||||||
return query.OrderBy(e => e.SortName);
|
return query.OrderBy(e => e.SortName);
|
||||||
}
|
}
|
||||||
@@ -1929,10 +1982,17 @@ public sealed class BaseItemRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(filter.Name))
|
if (!string.IsNullOrWhiteSpace(filter.Name))
|
||||||
|
{
|
||||||
|
if (filter.UseRawName == true)
|
||||||
|
{
|
||||||
|
baseQuery = baseQuery.Where(e => e.Name == filter.Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
var cleanName = GetCleanValue(filter.Name);
|
var cleanName = GetCleanValue(filter.Name);
|
||||||
baseQuery = baseQuery.Where(e => e.CleanName == cleanName);
|
baseQuery = baseQuery.Where(e => e.CleanName == cleanName);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// These are the same, for now
|
// These are the same, for now
|
||||||
var nameContains = filter.NameContains;
|
var nameContains = filter.NameContains;
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
|
|
||||||
ThrowIfInvalidUsername(newName);
|
ThrowIfInvalidUsername(newName);
|
||||||
|
|
||||||
if (user.Username.Equals(newName, StringComparison.OrdinalIgnoreCase))
|
if (user.Username.Equals(newName, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("The new and old names must be different.");
|
throw new ArgumentException("The new and old names must be different.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,11 @@ using Microsoft.AspNetCore.Builder;
|
|||||||
using Microsoft.AspNetCore.Cors.Infrastructure;
|
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.OpenApi.Any;
|
using Microsoft.OpenApi.Any;
|
||||||
using Microsoft.OpenApi.Interfaces;
|
using Microsoft.OpenApi.Interfaces;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Swashbuckle.AspNetCore.Swagger;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes;
|
using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes;
|
||||||
|
|
||||||
@@ -259,7 +261,8 @@ namespace Jellyfin.Server.Extensions
|
|||||||
c.OperationFilter<FileRequestFilter>();
|
c.OperationFilter<FileRequestFilter>();
|
||||||
c.OperationFilter<ParameterObsoleteFilter>();
|
c.OperationFilter<ParameterObsoleteFilter>();
|
||||||
c.DocumentFilter<AdditionalModelFilter>();
|
c.DocumentFilter<AdditionalModelFilter>();
|
||||||
});
|
})
|
||||||
|
.Replace(ServiceDescriptor.Transient<ISwaggerProvider, CachingOpenApiProvider>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddPolicy(this AuthorizationOptions authorizationOptions, string policyName, IAuthorizationRequirement authorizationRequirement)
|
private static void AddPolicy(this AuthorizationOptions authorizationOptions, string policyName, IAuthorizationRequirement authorizationRequirement)
|
||||||
|
|||||||
89
Jellyfin.Server/Filters/CachingOpenApiProvider.cs
Normal file
89
Jellyfin.Server/Filters/CachingOpenApiProvider.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Swashbuckle.AspNetCore.Swagger;
|
||||||
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Filters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OpenApi provider with caching.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class CachingOpenApiProvider : ISwaggerProvider
|
||||||
|
{
|
||||||
|
private const string CacheKey = "openapi.json";
|
||||||
|
|
||||||
|
private static readonly MemoryCacheEntryOptions _cacheOptions = new() { SlidingExpiration = TimeSpan.FromMinutes(5) };
|
||||||
|
private static readonly SemaphoreSlim _lock = new(1, 1);
|
||||||
|
private static readonly TimeSpan _lockTimeout = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
private readonly IMemoryCache _memoryCache;
|
||||||
|
private readonly SwaggerGenerator _swaggerGenerator;
|
||||||
|
private readonly SwaggerGeneratorOptions _swaggerGeneratorOptions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CachingOpenApiProvider"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="optionsAccessor">The options accessor.</param>
|
||||||
|
/// <param name="apiDescriptionsProvider">The api descriptions provider.</param>
|
||||||
|
/// <param name="schemaGenerator">The schema generator.</param>
|
||||||
|
/// <param name="memoryCache">The memory cache.</param>
|
||||||
|
public CachingOpenApiProvider(
|
||||||
|
IOptions<SwaggerGeneratorOptions> optionsAccessor,
|
||||||
|
IApiDescriptionGroupCollectionProvider apiDescriptionsProvider,
|
||||||
|
ISchemaGenerator schemaGenerator,
|
||||||
|
IMemoryCache memoryCache)
|
||||||
|
{
|
||||||
|
_swaggerGeneratorOptions = optionsAccessor.Value;
|
||||||
|
_swaggerGenerator = new SwaggerGenerator(_swaggerGeneratorOptions, apiDescriptionsProvider, schemaGenerator);
|
||||||
|
_memoryCache = memoryCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public OpenApiDocument GetSwagger(string documentName, string? host = null, string? basePath = null)
|
||||||
|
{
|
||||||
|
if (_memoryCache.TryGetValue(CacheKey, out OpenApiDocument? openApiDocument) && openApiDocument is not null)
|
||||||
|
{
|
||||||
|
return AdjustDocument(openApiDocument, host, basePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
var acquired = _lock.Wait(_lockTimeout);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_memoryCache.TryGetValue(CacheKey, out openApiDocument) && openApiDocument is not null)
|
||||||
|
{
|
||||||
|
return AdjustDocument(openApiDocument, host, basePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!acquired)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("OpenApi document is generating");
|
||||||
|
}
|
||||||
|
|
||||||
|
openApiDocument = _swaggerGenerator.GetSwagger(documentName);
|
||||||
|
_memoryCache.Set(CacheKey, openApiDocument, _cacheOptions);
|
||||||
|
return AdjustDocument(openApiDocument, host, basePath);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (acquired)
|
||||||
|
{
|
||||||
|
_lock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OpenApiDocument AdjustDocument(OpenApiDocument document, string? host, string? basePath)
|
||||||
|
{
|
||||||
|
document.Servers = _swaggerGeneratorOptions.Servers.Count != 0
|
||||||
|
? _swaggerGeneratorOptions.Servers
|
||||||
|
: string.IsNullOrEmpty(host) && string.IsNullOrEmpty(basePath)
|
||||||
|
? []
|
||||||
|
: [new OpenApiServer { Url = $"{host}{basePath}" }];
|
||||||
|
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Common</PackageId>
|
<PackageId>Jellyfin.Common</PackageId>
|
||||||
<VersionPrefix>10.11.3</VersionPrefix>
|
<VersionPrefix>10.11.4</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1620,12 +1620,17 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
return isAllowed;
|
return isAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxAllowedSubRating is not null)
|
if (!maxAllowedRating.HasValue)
|
||||||
{
|
{
|
||||||
return (ratingScore.SubScore ?? 0) <= maxAllowedSubRating && ratingScore.Score <= maxAllowedRating.Value;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !maxAllowedRating.HasValue || ratingScore.Score <= maxAllowedRating.Value;
|
if (ratingScore.Score != maxAllowedRating.Value)
|
||||||
|
{
|
||||||
|
return ratingScore.Score < maxAllowedRating.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !maxAllowedSubRating.HasValue || (ratingScore.SubScore ?? 0) <= maxAllowedSubRating.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParentalRatingScore GetParentalRatingScore()
|
public ParentalRatingScore GetParentalRatingScore()
|
||||||
|
|||||||
@@ -1406,13 +1406,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
.Where(e => query is null || UserViewBuilder.FilterItem(e, query))
|
.Where(e => query is null || UserViewBuilder.FilterItem(e, query))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
if (this is BoxSet && (query.OrderBy is null || query.OrderBy.Count == 0))
|
|
||||||
{
|
|
||||||
realChildren = realChildren
|
|
||||||
.OrderBy(e => e.PremiereDate ?? DateTime.MaxValue)
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
var childCount = realChildren.Length;
|
var childCount = realChildren.Length;
|
||||||
if (result.Count < limit)
|
if (result.Count < limit)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -125,6 +125,8 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
public bool? UseRawName { get; set; }
|
||||||
|
|
||||||
public string? Person { get; set; }
|
public string? Person { get; set; }
|
||||||
|
|
||||||
public Guid[] PersonIds { get; set; }
|
public Guid[] PersonIds { get; set; }
|
||||||
|
|||||||
@@ -136,6 +136,12 @@ namespace MediaBrowser.Controller.Entities.Movies
|
|||||||
return Sort(children, user).ToArray();
|
return Sort(children, user).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override IReadOnlyList<BaseItem> GetChildren(User user, bool includeLinkedChildren, out int totalItemCount, InternalItemsQuery query = null)
|
||||||
|
{
|
||||||
|
var children = base.GetChildren(user, includeLinkedChildren, out totalItemCount, query);
|
||||||
|
return Sort(children, user).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
public override IReadOnlyList<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query, out int totalCount)
|
public override IReadOnlyList<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query, out int totalCount)
|
||||||
{
|
{
|
||||||
var children = base.GetRecursiveChildren(user, query, out totalCount);
|
var children = base.GetRecursiveChildren(user, query, out totalCount);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Channels;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
@@ -29,7 +30,7 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Lock _taskLock = new();
|
private readonly Lock _taskLock = new();
|
||||||
|
|
||||||
private readonly BlockingCollection<TaskQueueItem> _tasks = new();
|
private readonly Channel<TaskQueueItem> _tasks = Channel.CreateUnbounded<TaskQueueItem>();
|
||||||
|
|
||||||
private volatile int _workCounter;
|
private volatile int _workCounter;
|
||||||
private Task? _cleanupTask;
|
private Task? _cleanupTask;
|
||||||
@@ -77,7 +78,7 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
|
|||||||
|
|
||||||
lock (_taskLock)
|
lock (_taskLock)
|
||||||
{
|
{
|
||||||
if (_tasks.Count > 0 || _workCounter > 0)
|
if (_tasks.Reader.Count > 0 || _workCounter > 0)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Delay cleanup task, operations still running.");
|
_logger.LogDebug("Delay cleanup task, operations still running.");
|
||||||
// tasks are still there so its still in use. Reschedule cleanup task.
|
// tasks are still there so its still in use. Reschedule cleanup task.
|
||||||
@@ -144,9 +145,9 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
|
|||||||
_deadlockDetector.Value = stopToken.TaskStop;
|
_deadlockDetector.Value = stopToken.TaskStop;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (var item in _tasks.GetConsumingEnumerable(stopToken.GlobalStop.Token))
|
while (!stopToken.GlobalStop.Token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
stopToken.GlobalStop.Token.ThrowIfCancellationRequested();
|
var item = await _tasks.Reader.ReadAsync(stopToken.GlobalStop.Token).ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var newWorkerLimit = Interlocked.Increment(ref _workCounter) > 0;
|
var newWorkerLimit = Interlocked.Increment(ref _workCounter) > 0;
|
||||||
@@ -242,7 +243,7 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
|
|||||||
};
|
};
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
if (ShouldForceSequentialOperation())
|
if (ShouldForceSequentialOperation() || _deadlockDetector.Value is not null)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Process sequentially.");
|
_logger.LogDebug("Process sequentially.");
|
||||||
try
|
try
|
||||||
@@ -264,36 +265,15 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
|
|||||||
for (var i = 0; i < workItems.Length; i++)
|
for (var i = 0; i < workItems.Length; i++)
|
||||||
{
|
{
|
||||||
var item = workItems[i]!;
|
var item = workItems[i]!;
|
||||||
_tasks.Add(item, CancellationToken.None);
|
await _tasks.Writer.WriteAsync(item, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_deadlockDetector.Value is not null)
|
|
||||||
{
|
|
||||||
_logger.LogDebug("Nested invocation detected, process in-place.");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// we are in a nested loop. There is no reason to spawn a task here as that would just lead to deadlocks and no additional concurrency is achieved
|
|
||||||
while (workItems.Any(e => !e.Done.Task.IsCompleted) && _tasks.TryTake(out var item, 200, _deadlockDetector.Value.Token))
|
|
||||||
{
|
|
||||||
await ProcessItem(item).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException) when (_deadlockDetector.Value.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
// operation is cancelled. Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogDebug("process in-place done.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Worker();
|
Worker();
|
||||||
_logger.LogDebug("Wait for {NoWorkers} to complete.", workItems.Length);
|
_logger.LogDebug("Wait for {NoWorkers} to complete.", workItems.Length);
|
||||||
await Task.WhenAll([.. workItems.Select(f => f.Done.Task)]).ConfigureAwait(false);
|
await Task.WhenAll([.. workItems.Select(f => f.Done.Task)]).ConfigureAwait(false);
|
||||||
_logger.LogDebug("{NoWorkers} completed.", workItems.Length);
|
_logger.LogDebug("{NoWorkers} completed.", workItems.Length);
|
||||||
ScheduleTaskCleanup();
|
ScheduleTaskCleanup();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
@@ -304,13 +284,12 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
|
|||||||
}
|
}
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
_tasks.CompleteAdding();
|
_tasks.Writer.Complete();
|
||||||
foreach (var item in _taskRunners)
|
foreach (var item in _taskRunners)
|
||||||
{
|
{
|
||||||
await item.Key.CancelAsync().ConfigureAwait(false);
|
await item.Key.CancelAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_tasks.Dispose();
|
|
||||||
if (_cleanupTask is not null)
|
if (_cleanupTask is not null)
|
||||||
{
|
{
|
||||||
await _cleanupTask.ConfigureAwait(false);
|
await _cleanupTask.ConfigureAwait(false);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Controller</PackageId>
|
<PackageId>Jellyfin.Controller</PackageId>
|
||||||
<VersionPrefix>10.11.3</VersionPrefix>
|
<VersionPrefix>10.11.4</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -5949,28 +5949,37 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
|
var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
|
||||||
var swapOutputWandH = doRkVppTranspose && swapWAndH;
|
var swapOutputWandH = doRkVppTranspose && swapWAndH;
|
||||||
var outFormat = doOclTonemap ? "p010" : (isMjpegEncoder ? "bgra" : "nv12"); // RGA only support full range in rgb fmts
|
var outFormat = doOclTonemap ? "p010" : "nv12";
|
||||||
var hwScaleFilter = GetHwScaleFilter("vpp", "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
|
var hwScaleFilter = GetHwScaleFilter("vpp", "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
|
||||||
var doScaling = GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
|
var doScaling = !string.IsNullOrEmpty(GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH));
|
||||||
|
|
||||||
if (!hasSubs
|
if (!hasSubs
|
||||||
|| doRkVppTranspose
|
|| doRkVppTranspose
|
||||||
|| !isFullAfbcPipeline
|
|| !isFullAfbcPipeline
|
||||||
|| !string.IsNullOrEmpty(doScaling))
|
|| doScaling)
|
||||||
{
|
{
|
||||||
|
var isScaleRatioSupported = IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
|
||||||
|
|
||||||
// RGA3 hardware only support (1/8 ~ 8) scaling in each blit operation,
|
// RGA3 hardware only support (1/8 ~ 8) scaling in each blit operation,
|
||||||
// but in Trickplay there's a case: (3840/320 == 12), enable 2pass for it
|
// but in Trickplay there's a case: (3840/320 == 12), enable 2pass for it
|
||||||
if (!string.IsNullOrEmpty(doScaling)
|
if (doScaling && !isScaleRatioSupported)
|
||||||
&& !IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f))
|
|
||||||
{
|
{
|
||||||
// Vendor provided BSP kernel has an RGA driver bug that causes the output to be corrupted for P010 format.
|
// Vendor provided BSP kernel has an RGA driver bug that causes the output to be corrupted for P010 format.
|
||||||
// Use NV15 instead of P010 to avoid the issue.
|
// Use NV15 instead of P010 to avoid the issue.
|
||||||
// SDR inputs are using BGRA formats already which is not affected.
|
// SDR inputs are using BGRA formats already which is not affected.
|
||||||
var intermediateFormat = string.Equals(outFormat, "p010", StringComparison.OrdinalIgnoreCase) ? "nv15" : outFormat;
|
var intermediateFormat = doOclTonemap ? "nv15" : (isMjpegEncoder ? "bgra" : outFormat);
|
||||||
var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={intermediateFormat}:force_original_aspect_ratio=increase:force_divisible_by=4:afbc=1";
|
var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={intermediateFormat}:force_original_aspect_ratio=increase:force_divisible_by=4:afbc=1";
|
||||||
mainFilters.Add(hwScaleFilterFirstPass);
|
mainFilters.Add(hwScaleFilterFirstPass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The RKMPP MJPEG encoder on some newer chip models no longer supports RGB input.
|
||||||
|
// Use 2pass here to enable RGA output of full-range YUV in the 2nd pass.
|
||||||
|
if (isMjpegEncoder && !doOclTonemap && ((doScaling && isScaleRatioSupported) || !doScaling))
|
||||||
|
{
|
||||||
|
var hwScaleFilterFirstPass = "vpp_rkrga=format=bgra:afbc=1";
|
||||||
|
mainFilters.Add(hwScaleFilterFirstPass);
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
|
if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
|
||||||
{
|
{
|
||||||
hwScaleFilter += $":transpose={transposeDir}";
|
hwScaleFilter += $":transpose={transposeDir}";
|
||||||
@@ -7030,8 +7039,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var accelType = GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
|
// there's an issue about AV1 AFBC on RK3588, disable it for now until it's fixed upstream
|
||||||
return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Empty);
|
return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Model</PackageId>
|
<PackageId>Jellyfin.Model</PackageId>
|
||||||
<VersionPrefix>10.11.3</VersionPrefix>
|
<VersionPrefix>10.11.4</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -88,7 +88,15 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
singular.AddRange(item.GetImages(ImageType.Backdrop));
|
foreach (var backdrop in item.GetImages(ImageType.Backdrop))
|
||||||
|
{
|
||||||
|
var imageInMetadataFolder = backdrop.Path.StartsWith(itemMetadataPath, StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (imageInMetadataFolder || canDeleteLocal || item.IsSaveLocalMetadataEnabled())
|
||||||
|
{
|
||||||
|
singular.Add(backdrop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PruneImages(item, singular);
|
PruneImages(item, singular);
|
||||||
|
|
||||||
return singular.Count > 0;
|
return singular.Count > 0;
|
||||||
@@ -466,11 +474,37 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hasBackdrop = false;
|
||||||
|
bool backdropStoredWithMedia = false;
|
||||||
|
|
||||||
|
foreach (var image in images)
|
||||||
|
{
|
||||||
|
if (image.Type != ImageType.Backdrop)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasBackdrop = true;
|
||||||
|
|
||||||
|
if (item.ContainingFolderPath is not null && item.ContainingFolderPath.Contains(Path.GetDirectoryName(image.FileInfo.FullName), StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
backdropStoredWithMedia = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasBackdrop)
|
||||||
|
{
|
||||||
if (UpdateMultiImages(item, images, ImageType.Backdrop))
|
if (UpdateMultiImages(item, images, ImageType.Backdrop))
|
||||||
{
|
{
|
||||||
changed = true;
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backdropStoredWithMedia)
|
||||||
|
{
|
||||||
foundImageTypes.Add(ImageType.Backdrop);
|
foundImageTypes.Add(ImageType.Backdrop);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (foundImageTypes.Count > 0)
|
if (foundImageTypes.Count > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -721,8 +721,6 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_libraryManager.CreateItem(item, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
[assembly: AssemblyVersion("10.11.3")]
|
[assembly: AssemblyVersion("10.11.4")]
|
||||||
[assembly: AssemblyFileVersion("10.11.3")]
|
[assembly: AssemblyFileVersion("10.11.4")]
|
||||||
|
|||||||
@@ -209,8 +209,11 @@ public class SkiaEncoder : IImageEncoder
|
|||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var codec = SKCodec.Create(safePath, out var result);
|
SKCodec? codec = null;
|
||||||
|
bool isSafePathTemp = !string.Equals(Path.GetFullPath(safePath), Path.GetFullPath(path), StringComparison.OrdinalIgnoreCase);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
codec = SKCodec.Create(safePath, out var result);
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
case SKCodecResult.Success:
|
case SKCodecResult.Success:
|
||||||
@@ -231,7 +234,6 @@ public class SkiaEncoder : IImageEncoder
|
|||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
var boundsInfo = SKBitmap.DecodeBounds(safePath);
|
var boundsInfo = SKBitmap.DecodeBounds(safePath);
|
||||||
|
|
||||||
if (boundsInfo.Width > 0 && boundsInfo.Height > 0)
|
if (boundsInfo.Width > 0 && boundsInfo.Height > 0)
|
||||||
{
|
{
|
||||||
return new ImageDimensions(boundsInfo.Width, boundsInfo.Height);
|
return new ImageDimensions(boundsInfo.Width, boundsInfo.Height);
|
||||||
@@ -241,10 +243,38 @@ public class SkiaEncoder : IImageEncoder
|
|||||||
"Unable to determine image dimensions for {FilePath}: {SkCodecResult}",
|
"Unable to determine image dimensions for {FilePath}: {SkCodecResult}",
|
||||||
path,
|
path,
|
||||||
result);
|
result);
|
||||||
|
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
codec?.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "Error by closing codec for {FilePath}", safePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSafePathTemp)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(safePath))
|
||||||
|
{
|
||||||
|
File.Delete(safePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "Unable to remove temporary file '{TempPath}'", safePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
/// <exception cref="ArgumentNullException">The path is null.</exception>
|
/// <exception cref="ArgumentNullException">The path is null.</exception>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Extensions</PackageId>
|
<PackageId>Jellyfin.Extensions</PackageId>
|
||||||
<VersionPrefix>10.11.3</VersionPrefix>
|
<VersionPrefix>10.11.4</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,30 +1,81 @@
|
|||||||
|
using Emby.Server.Implementations.Library;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Implementations.Tests.Library;
|
namespace Jellyfin.Server.Implementations.Tests.Library;
|
||||||
|
|
||||||
public class DotIgnoreIgnoreRuleTest
|
public class DotIgnoreIgnoreRuleTest
|
||||||
{
|
{
|
||||||
[Fact]
|
private static readonly string[] _rule1 = ["SPs"];
|
||||||
public void Test()
|
private static readonly string[] _rule2 = ["SPs", "!thebestshot.mkv"];
|
||||||
|
private static readonly string[] _rule3 = ["*.txt", @"{\colortbl;\red255\green255\blue255;}", "videos/", @"\invalid\escape\sequence", "*.mkv"];
|
||||||
|
private static readonly string[] _rule4 = [@"{\colortbl;\red255\green255\blue255;}", @"\invalid\escape\sequence"];
|
||||||
|
|
||||||
|
public static TheoryData<string[], string, bool, bool> CheckIgnoreRulesTestData =>
|
||||||
|
new()
|
||||||
{
|
{
|
||||||
var ignore = new Ignore.Ignore();
|
// Basic pattern matching
|
||||||
ignore.Add("SPs");
|
{ _rule1, "f:/cd/sps/ffffff.mkv", false, true },
|
||||||
Assert.True(ignore.IsIgnored("f:/cd/sps/ffffff.mkv"));
|
{ _rule1, "cd/sps/ffffff.mkv", false, true },
|
||||||
Assert.True(ignore.IsIgnored("cd/sps/ffffff.mkv"));
|
{ _rule1, "/cd/sps/ffffff.mkv", false, true },
|
||||||
Assert.True(ignore.IsIgnored("/cd/sps/ffffff.mkv"));
|
|
||||||
|
// Negate pattern
|
||||||
|
{ _rule2, "f:/cd/sps/ffffff.mkv", false, true },
|
||||||
|
{ _rule2, "cd/sps/ffffff.mkv", false, true },
|
||||||
|
{ _rule2, "/cd/sps/ffffff.mkv", false, true },
|
||||||
|
{ _rule2, "f:/cd/sps/thebestshot.mkv", false, false },
|
||||||
|
{ _rule2, "cd/sps/thebestshot.mkv", false, false },
|
||||||
|
{ _rule2, "/cd/sps/thebestshot.mkv", false, false },
|
||||||
|
|
||||||
|
// Mixed valid and invalid patterns - skips invalid, applies valid
|
||||||
|
{ _rule3, "test.txt", false, true },
|
||||||
|
{ _rule3, "videos/movie.mp4", false, true },
|
||||||
|
{ _rule3, "movie.mkv", false, true },
|
||||||
|
{ _rule3, "test.mp3", false, false },
|
||||||
|
|
||||||
|
// Only invalid patterns - falls back to ignore all
|
||||||
|
{ _rule4, "any-file.txt", false, true },
|
||||||
|
{ _rule4, "any/path/to/file.mkv", false, true },
|
||||||
|
};
|
||||||
|
|
||||||
|
public static TheoryData<string[], string, bool, bool> WindowsPathNormalizationTestData =>
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
// Windows paths with backslashes - should match when normalizePath is true
|
||||||
|
{ _rule1, @"C:\cd\sps\ffffff.mkv", false, true },
|
||||||
|
{ _rule1, @"D:\media\sps\movie.mkv", false, true },
|
||||||
|
{ _rule1, @"\\server\share\sps\file.mkv", false, true },
|
||||||
|
|
||||||
|
// Negate pattern with Windows paths
|
||||||
|
{ _rule2, @"C:\cd\sps\ffffff.mkv", false, true },
|
||||||
|
{ _rule2, @"C:\cd\sps\thebestshot.mkv", false, false },
|
||||||
|
|
||||||
|
// Directory matching with Windows paths
|
||||||
|
{ _rule3, @"C:\videos\movie.mp4", false, true },
|
||||||
|
{ _rule3, @"D:\documents\test.txt", false, true },
|
||||||
|
{ _rule3, @"E:\music\song.mp3", false, false },
|
||||||
|
};
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(CheckIgnoreRulesTestData))]
|
||||||
|
public void CheckIgnoreRules_ReturnsExpectedResult(string[] rules, string path, bool isDirectory, bool expectedIgnored)
|
||||||
|
{
|
||||||
|
Assert.Equal(expectedIgnored, DotIgnoreIgnoreRule.CheckIgnoreRules(path, rules, isDirectory));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Theory]
|
||||||
public void TestNegatePattern()
|
[MemberData(nameof(WindowsPathNormalizationTestData))]
|
||||||
|
public void CheckIgnoreRules_WithWindowsPaths_NormalizesBackslashes(string[] rules, string path, bool isDirectory, bool expectedIgnored)
|
||||||
{
|
{
|
||||||
var ignore = new Ignore.Ignore();
|
// With normalizePath=true, backslashes should be converted to forward slashes
|
||||||
ignore.Add("SPs");
|
Assert.Equal(expectedIgnored, DotIgnoreIgnoreRule.CheckIgnoreRules(path, rules, isDirectory, normalizePath: true));
|
||||||
ignore.Add("!thebestshot.mkv");
|
}
|
||||||
Assert.True(ignore.IsIgnored("f:/cd/sps/ffffff.mkv"));
|
|
||||||
Assert.True(ignore.IsIgnored("cd/sps/ffffff.mkv"));
|
[Theory]
|
||||||
Assert.True(ignore.IsIgnored("/cd/sps/ffffff.mkv"));
|
[InlineData(@"C:\cd\sps\ffffff.mkv")]
|
||||||
Assert.True(!ignore.IsIgnored("f:/cd/sps/thebestshot.mkv"));
|
[InlineData(@"D:\media\sps\movie.mkv")]
|
||||||
Assert.True(!ignore.IsIgnored("cd/sps/thebestshot.mkv"));
|
public void CheckIgnoreRules_WithWindowsPaths_WithoutNormalization_DoesNotMatch(string path)
|
||||||
Assert.True(!ignore.IsIgnored("/cd/sps/thebestshot.mkv"));
|
{
|
||||||
|
// Without normalization, Windows paths with backslashes won't match patterns expecting forward slashes
|
||||||
|
Assert.False(DotIgnoreIgnoreRule.CheckIgnoreRules(path, _rule1, isDirectory: false, normalizePath: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user