mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-12-17 06:23:03 +03:00
Compare commits
1 Commits
v10.11.5
...
only-count
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a78c3385c9 |
@@ -3,7 +3,7 @@
|
|||||||
"isRoot": true,
|
"isRoot": true,
|
||||||
"tools": {
|
"tools": {
|
||||||
"dotnet-ef": {
|
"dotnet-ef": {
|
||||||
"version": "9.0.11",
|
"version": "9.0.10",
|
||||||
"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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '9.0.x'
|
dotnet-version: '9.0.x'
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6
|
uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
queries: +security-extended
|
queries: +security-extended
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6
|
uses: github/codeql-action/autobuild@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6
|
uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
||||||
|
|||||||
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
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@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||||
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@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
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@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||||
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@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
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@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.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@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
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@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||||
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@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
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@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||||
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@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
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@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.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@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.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@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.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@823bd89e131d8d508129f9443cad5855e9ba96f0 # v1.2.4
|
uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2
|
||||||
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@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.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@823bd89e131d8d508129f9443cad5855e9ba96f0 # v1.2.4
|
uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2
|
||||||
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
- uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
- uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||||
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@ee0ae774f6d3afedcbd1683c1ab21b83670bdf8e # v5.5.1
|
uses: danielpalme/ReportGenerator-GitHub-Action@9870ed167742d546b99962ff815fcc1098355ed8 # v5.4.17
|
||||||
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
repository: jellyfin/jellyfin-triage-script
|
repository: jellyfin/jellyfin-triage-script
|
||||||
- name: install python
|
- name: install python
|
||||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.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@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
|
||||||
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
repository: jellyfin/jellyfin-triage-script
|
repository: jellyfin/jellyfin-triage-script
|
||||||
- name: install python
|
- name: install python
|
||||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.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@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
|
||||||
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ env.TAG_BRANCH }}
|
ref: ${{ env.TAG_BRANCH }}
|
||||||
|
|
||||||
|
|||||||
@@ -117,7 +117,6 @@
|
|||||||
- [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)
|
||||||
@@ -206,7 +205,6 @@
|
|||||||
- [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.8" />
|
<PackageVersion Include="AsyncKeyedLock" Version="7.1.7" />
|
||||||
<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.2" />
|
<PackageVersion Include="FsCheck.Xunit" Version="3.3.1" />
|
||||||
<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.11" />
|
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.10" />
|
||||||
<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.11" />
|
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
|
||||||
<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.5" />
|
<PackageVersion Include="Polly" Version="8.6.4" />
|
||||||
<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.1.1" />
|
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
<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.11" />
|
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.10" />
|
||||||
<PackageVersion Include="System.Text.Json" Version="9.0.11" />
|
<PackageVersion Include="System.Text.Json" Version="9.0.10" />
|
||||||
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.11" />
|
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.10" />
|
||||||
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
||||||
<PackageVersion Include="z440.atl.core" Version="7.9.0" />
|
<PackageVersion Include="z440.atl.core" Version="7.6.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.5</VersionPrefix>
|
<VersionPrefix>10.11.3</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,6 +1,5 @@
|
|||||||
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;
|
||||||
@@ -71,55 +70,12 @@ 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 = normalizePath ? path.NormalizePath('/') : path;
|
var pathToCheck = IsWindows ? path.NormalizePath('/') : path;
|
||||||
|
|
||||||
// Add trailing slash for directories to match "folder/"
|
// Add trailing slash for directories to match "folder/"
|
||||||
if (isDirectory)
|
if (isDirectory)
|
||||||
|
|||||||
@@ -1058,7 +1058,6 @@ 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,7 +23,6 @@ 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;
|
||||||
@@ -701,18 +700,7 @@ 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);
|
||||||
|
|
||||||
var filePath = item.Path;
|
return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path), filename, true);
|
||||||
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>(mediaSourceId ?? itemId, User.GetUserId());
|
var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|||||||
@@ -159,13 +159,6 @@ 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.5</VersionPrefix>
|
<VersionPrefix>10.11.3</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>
|
||||||
|
|||||||
@@ -410,25 +410,10 @@ public sealed class BaseItemRepository
|
|||||||
|
|
||||||
private static IQueryable<BaseItemEntity> ApplyNavigations(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter)
|
private static IQueryable<BaseItemEntity> ApplyNavigations(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter)
|
||||||
{
|
{
|
||||||
if (filter.TrailerTypes.Length > 0 || filter.IncludeItemTypes.Contains(BaseItemKind.Trailer))
|
dbQuery = dbQuery.Include(e => e.TrailerTypes)
|
||||||
{
|
.Include(e => e.Provider)
|
||||||
dbQuery = dbQuery.Include(e => e.TrailerTypes);
|
.Include(e => e.LockedFields)
|
||||||
}
|
.Include(e => e.UserData);
|
||||||
|
|
||||||
if (filter.DtoOptions.ContainsField(ItemFields.ProviderIds))
|
|
||||||
{
|
|
||||||
dbQuery = dbQuery.Include(e => e.Provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter.DtoOptions.ContainsField(ItemFields.Settings))
|
|
||||||
{
|
|
||||||
dbQuery = dbQuery.Include(e => e.LockedFields);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter.DtoOptions.EnableUserData)
|
|
||||||
{
|
|
||||||
dbQuery = dbQuery.Include(e => e.UserData);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter.DtoOptions.EnableImages)
|
if (filter.DtoOptions.EnableImages)
|
||||||
{
|
{
|
||||||
@@ -1544,14 +1529,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.Where(e => e.OrderBy != ItemSortBy.Default).ToArray();
|
var orderBy = filter.OrderBy;
|
||||||
var hasSearch = !string.IsNullOrEmpty(filter.SearchTerm);
|
var hasSearch = !string.IsNullOrEmpty(filter.SearchTerm);
|
||||||
|
|
||||||
if (hasSearch)
|
if (hasSearch)
|
||||||
{
|
{
|
||||||
orderBy = [(ItemSortBy.SortName, SortOrder.Ascending), .. orderBy];
|
orderBy = filter.OrderBy = [(ItemSortBy.SortName, SortOrder.Ascending), .. orderBy];
|
||||||
}
|
}
|
||||||
else if (orderBy.Length == 0)
|
else if (orderBy.Count == 0)
|
||||||
{
|
{
|
||||||
return query.OrderBy(e => e.SortName);
|
return query.OrderBy(e => e.SortName);
|
||||||
}
|
}
|
||||||
@@ -1945,15 +1930,8 @@ public sealed class BaseItemRepository
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(filter.Name))
|
if (!string.IsNullOrWhiteSpace(filter.Name))
|
||||||
{
|
{
|
||||||
if (filter.UseRawName == true)
|
var cleanName = GetCleanValue(filter.Name);
|
||||||
{
|
baseQuery = baseQuery.Where(e => e.CleanName == cleanName);
|
||||||
baseQuery = baseQuery.Where(e => e.Name == filter.Name);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var cleanName = GetCleanValue(filter.Name);
|
|
||||||
baseQuery = baseQuery.Where(e => e.CleanName == cleanName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// These are the same, for now
|
// These are the same, for now
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
|
|
||||||
ThrowIfInvalidUsername(newName);
|
ThrowIfInvalidUsername(newName);
|
||||||
|
|
||||||
if (user.Username.Equals(newName, StringComparison.Ordinal))
|
if (user.Username.Equals(newName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("The new and old names must be different.");
|
throw new ArgumentException("The new and old names must be different.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,11 +33,9 @@ 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;
|
||||||
|
|
||||||
@@ -261,8 +259,7 @@ 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)
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
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.5</VersionPrefix>
|
<VersionPrefix>10.11.3</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,17 +1620,12 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
return isAllowed;
|
return isAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!maxAllowedRating.HasValue)
|
if (maxAllowedSubRating is not null)
|
||||||
{
|
{
|
||||||
return true;
|
return (ratingScore.SubScore ?? 0) <= maxAllowedSubRating && ratingScore.Score <= maxAllowedRating.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ratingScore.Score != maxAllowedRating.Value)
|
return !maxAllowedRating.HasValue || ratingScore.Score <= maxAllowedRating.Value;
|
||||||
{
|
|
||||||
return ratingScore.Score < maxAllowedRating.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !maxAllowedSubRating.HasValue || (ratingScore.SubScore ?? 0) <= maxAllowedSubRating.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParentalRatingScore GetParentalRatingScore()
|
public ParentalRatingScore GetParentalRatingScore()
|
||||||
|
|||||||
@@ -668,34 +668,22 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = GetItems(new InternalItemsQuery(user)
|
return LibraryManager.GetCount(new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
Recursive = false,
|
Recursive = false,
|
||||||
Limit = 0,
|
Parent = this
|
||||||
Parent = this,
|
|
||||||
DtoOptions = new DtoOptions(false)
|
|
||||||
{
|
|
||||||
EnableImages = false
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return result.TotalRecordCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual int GetRecursiveChildCount(User user)
|
public virtual int GetRecursiveChildCount(User user)
|
||||||
{
|
{
|
||||||
return GetItems(new InternalItemsQuery(user)
|
return LibraryManager.GetCount(new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
|
Parent = this,
|
||||||
IsFolder = false,
|
IsFolder = false,
|
||||||
IsVirtualItem = false,
|
IsVirtualItem = false
|
||||||
EnableTotalRecordCount = true,
|
});
|
||||||
Limit = 0,
|
|
||||||
DtoOptions = new DtoOptions(false)
|
|
||||||
{
|
|
||||||
EnableImages = false
|
|
||||||
}
|
|
||||||
}).TotalRecordCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<BaseItem> QueryRecursive(InternalItemsQuery query)
|
public QueryResult<BaseItem> QueryRecursive(InternalItemsQuery query)
|
||||||
@@ -1406,6 +1394,13 @@ 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,8 +125,6 @@ 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; }
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ namespace MediaBrowser.Controller.Entities.Movies
|
|||||||
|
|
||||||
if (sortBy == ItemSortBy.Default)
|
if (sortBy == ItemSortBy.Default)
|
||||||
{
|
{
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
return LibraryManager.Sort(items, user, new[] { sortBy }, SortOrder.Ascending);
|
return LibraryManager.Sort(items, user, new[] { sortBy }, SortOrder.Ascending);
|
||||||
@@ -136,12 +136,6 @@ 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,7 +4,6 @@ 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;
|
||||||
@@ -30,7 +29,7 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Lock _taskLock = new();
|
private readonly Lock _taskLock = new();
|
||||||
|
|
||||||
private readonly Channel<TaskQueueItem> _tasks = Channel.CreateUnbounded<TaskQueueItem>();
|
private readonly BlockingCollection<TaskQueueItem> _tasks = new();
|
||||||
|
|
||||||
private volatile int _workCounter;
|
private volatile int _workCounter;
|
||||||
private Task? _cleanupTask;
|
private Task? _cleanupTask;
|
||||||
@@ -78,7 +77,7 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
|
|||||||
|
|
||||||
lock (_taskLock)
|
lock (_taskLock)
|
||||||
{
|
{
|
||||||
if (_tasks.Reader.Count > 0 || _workCounter > 0)
|
if (_tasks.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.
|
||||||
@@ -145,9 +144,9 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
|
|||||||
_deadlockDetector.Value = stopToken.TaskStop;
|
_deadlockDetector.Value = stopToken.TaskStop;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (!stopToken.GlobalStop.Token.IsCancellationRequested)
|
foreach (var item in _tasks.GetConsumingEnumerable(stopToken.GlobalStop.Token))
|
||||||
{
|
{
|
||||||
var item = await _tasks.Reader.ReadAsync(stopToken.GlobalStop.Token).ConfigureAwait(false);
|
stopToken.GlobalStop.Token.ThrowIfCancellationRequested();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var newWorkerLimit = Interlocked.Increment(ref _workCounter) > 0;
|
var newWorkerLimit = Interlocked.Increment(ref _workCounter) > 0;
|
||||||
@@ -243,7 +242,7 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
|
|||||||
};
|
};
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
if (ShouldForceSequentialOperation() || _deadlockDetector.Value is not null)
|
if (ShouldForceSequentialOperation())
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Process sequentially.");
|
_logger.LogDebug("Process sequentially.");
|
||||||
try
|
try
|
||||||
@@ -265,14 +264,35 @@ 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]!;
|
||||||
await _tasks.Writer.WriteAsync(item, CancellationToken.None).ConfigureAwait(false);
|
_tasks.Add(item, CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
Worker();
|
if (_deadlockDetector.Value is not null)
|
||||||
_logger.LogDebug("Wait for {NoWorkers} to complete.", workItems.Length);
|
{
|
||||||
await Task.WhenAll([.. workItems.Select(f => f.Done.Task)]).ConfigureAwait(false);
|
_logger.LogDebug("Nested invocation detected, process in-place.");
|
||||||
_logger.LogDebug("{NoWorkers} completed.", workItems.Length);
|
try
|
||||||
ScheduleTaskCleanup();
|
{
|
||||||
|
// 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();
|
||||||
|
_logger.LogDebug("Wait for {NoWorkers} to complete.", workItems.Length);
|
||||||
|
await Task.WhenAll([.. workItems.Select(f => f.Done.Task)]).ConfigureAwait(false);
|
||||||
|
_logger.LogDebug("{NoWorkers} completed.", workItems.Length);
|
||||||
|
ScheduleTaskCleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -284,12 +304,13 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
|
|||||||
}
|
}
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
_tasks.Writer.Complete();
|
_tasks.CompleteAdding();
|
||||||
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.5</VersionPrefix>
|
<VersionPrefix>10.11.3</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,37 +5949,28 @@ 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" : "nv12";
|
var outFormat = doOclTonemap ? "p010" : (isMjpegEncoder ? "bgra" : "nv12"); // RGA only support full range in rgb fmts
|
||||||
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 = !string.IsNullOrEmpty(GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH));
|
var doScaling = GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
|
||||||
|
|
||||||
if (!hasSubs
|
if (!hasSubs
|
||||||
|| doRkVppTranspose
|
|| doRkVppTranspose
|
||||||
|| !isFullAfbcPipeline
|
|| !isFullAfbcPipeline
|
||||||
|| doScaling)
|
|| !string.IsNullOrEmpty(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 (doScaling && !isScaleRatioSupported)
|
if (!string.IsNullOrEmpty(doScaling)
|
||||||
|
&& !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 = doOclTonemap ? "nv15" : (isMjpegEncoder ? "bgra" : outFormat);
|
var intermediateFormat = string.Equals(outFormat, "p010", StringComparison.OrdinalIgnoreCase) ? "nv15" : 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}";
|
||||||
@@ -7039,8 +7030,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// there's an issue about AV1 AFBC on RK3588, disable it for now until it's fixed upstream
|
var accelType = GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
|
||||||
return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
|
return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.5</VersionPrefix>
|
<VersionPrefix>10.11.3</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,15 +88,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var backdrop in item.GetImages(ImageType.Backdrop))
|
singular.AddRange(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;
|
||||||
@@ -474,36 +466,10 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasBackdrop = false;
|
if (UpdateMultiImages(item, images, ImageType.Backdrop))
|
||||||
bool backdropStoredWithMedia = false;
|
|
||||||
|
|
||||||
foreach (var image in images)
|
|
||||||
{
|
{
|
||||||
if (image.Type != ImageType.Backdrop)
|
changed = true;
|
||||||
{
|
foundImageTypes.Add(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))
|
|
||||||
{
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backdropStoredWithMedia)
|
|
||||||
{
|
|
||||||
foundImageTypes.Add(ImageType.Backdrop);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundImageTypes.Count > 0)
|
if (foundImageTypes.Count > 0)
|
||||||
|
|||||||
@@ -721,6 +721,8 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_libraryManager.CreateItem(item, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
[assembly: AssemblyVersion("10.11.5")]
|
[assembly: AssemblyVersion("10.11.3")]
|
||||||
[assembly: AssemblyFileVersion("10.11.5")]
|
[assembly: AssemblyFileVersion("10.11.3")]
|
||||||
|
|||||||
@@ -209,69 +209,39 @@ public class SkiaEncoder : IImageEncoder
|
|||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
SKCodec? codec = null;
|
using var codec = SKCodec.Create(safePath, out var result);
|
||||||
bool isSafePathTemp = !string.Equals(Path.GetFullPath(safePath), Path.GetFullPath(path), StringComparison.OrdinalIgnoreCase);
|
|
||||||
try
|
switch (result)
|
||||||
{
|
{
|
||||||
codec = SKCodec.Create(safePath, out var result);
|
case SKCodecResult.Success:
|
||||||
switch (result)
|
// Skia/SkiaSharp edge‑case: when the image header is parsed but the actual pixel
|
||||||
|
// decode fails (truncated JPEG/PNG, exotic ICC/EXIF, CMYK without color‑transform, etc.)
|
||||||
|
// `SKCodec.Create` returns a *non‑null* codec together with
|
||||||
|
// SKCodecResult.InternalError. The header still contains valid dimensions,
|
||||||
|
// which is all we need here – so we fall back to them instead of aborting.
|
||||||
|
// See e.g. Skia bugs #4139, #6092.
|
||||||
|
case SKCodecResult.InternalError when codec is not null:
|
||||||
|
var info = codec.Info;
|
||||||
|
return new ImageDimensions(info.Width, info.Height);
|
||||||
|
|
||||||
|
case SKCodecResult.Unimplemented:
|
||||||
|
_logger.LogDebug("Image format not supported: {FilePath}", path);
|
||||||
|
return default;
|
||||||
|
|
||||||
|
default:
|
||||||
{
|
{
|
||||||
case SKCodecResult.Success:
|
var boundsInfo = SKBitmap.DecodeBounds(safePath);
|
||||||
// Skia/SkiaSharp edge‑case: when the image header is parsed but the actual pixel
|
|
||||||
// decode fails (truncated JPEG/PNG, exotic ICC/EXIF, CMYK without color‑transform, etc.)
|
|
||||||
// `SKCodec.Create` returns a *non‑null* codec together with
|
|
||||||
// SKCodecResult.InternalError. The header still contains valid dimensions,
|
|
||||||
// which is all we need here – so we fall back to them instead of aborting.
|
|
||||||
// See e.g. Skia bugs #4139, #6092.
|
|
||||||
case SKCodecResult.InternalError when codec is not null:
|
|
||||||
var info = codec.Info;
|
|
||||||
return new ImageDimensions(info.Width, info.Height);
|
|
||||||
|
|
||||||
case SKCodecResult.Unimplemented:
|
if (boundsInfo.Width > 0 && boundsInfo.Height > 0)
|
||||||
_logger.LogDebug("Image format not supported: {FilePath}", path);
|
|
||||||
return default;
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
{
|
||||||
var boundsInfo = SKBitmap.DecodeBounds(safePath);
|
return new ImageDimensions(boundsInfo.Width, boundsInfo.Height);
|
||||||
if (boundsInfo.Width > 0 && boundsInfo.Height > 0)
|
|
||||||
{
|
|
||||||
return new ImageDimensions(boundsInfo.Width, boundsInfo.Height);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogWarning(
|
|
||||||
"Unable to determine image dimensions for {FilePath}: {SkCodecResult}",
|
|
||||||
path,
|
|
||||||
result);
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
codec?.Dispose();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogDebug(ex, "Error by closing codec for {FilePath}", safePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSafePathTemp)
|
_logger.LogWarning(
|
||||||
{
|
"Unable to determine image dimensions for {FilePath}: {SkCodecResult}",
|
||||||
try
|
path,
|
||||||
{
|
result);
|
||||||
if (File.Exists(safePath))
|
return default;
|
||||||
{
|
|
||||||
File.Delete(safePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogDebug(ex, "Unable to remove temporary file '{TempPath}'", safePath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.5</VersionPrefix>
|
<VersionPrefix>10.11.3</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,81 +1,30 @@
|
|||||||
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
|
||||||
{
|
{
|
||||||
private static readonly string[] _rule1 = ["SPs"];
|
[Fact]
|
||||||
private static readonly string[] _rule2 = ["SPs", "!thebestshot.mkv"];
|
public void Test()
|
||||||
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()
|
|
||||||
{
|
|
||||||
// Basic pattern matching
|
|
||||||
{ _rule1, "f:/cd/sps/ffffff.mkv", false, true },
|
|
||||||
{ _rule1, "cd/sps/ffffff.mkv", false, true },
|
|
||||||
{ _rule1, "/cd/sps/ffffff.mkv", false, true },
|
|
||||||
|
|
||||||
// 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));
|
var ignore = new Ignore.Ignore();
|
||||||
|
ignore.Add("SPs");
|
||||||
|
Assert.True(ignore.IsIgnored("f:/cd/sps/ffffff.mkv"));
|
||||||
|
Assert.True(ignore.IsIgnored("cd/sps/ffffff.mkv"));
|
||||||
|
Assert.True(ignore.IsIgnored("/cd/sps/ffffff.mkv"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[MemberData(nameof(WindowsPathNormalizationTestData))]
|
public void TestNegatePattern()
|
||||||
public void CheckIgnoreRules_WithWindowsPaths_NormalizesBackslashes(string[] rules, string path, bool isDirectory, bool expectedIgnored)
|
|
||||||
{
|
{
|
||||||
// With normalizePath=true, backslashes should be converted to forward slashes
|
var ignore = new Ignore.Ignore();
|
||||||
Assert.Equal(expectedIgnored, DotIgnoreIgnoreRule.CheckIgnoreRules(path, rules, isDirectory, normalizePath: true));
|
ignore.Add("SPs");
|
||||||
}
|
ignore.Add("!thebestshot.mkv");
|
||||||
|
Assert.True(ignore.IsIgnored("f:/cd/sps/ffffff.mkv"));
|
||||||
[Theory]
|
Assert.True(ignore.IsIgnored("cd/sps/ffffff.mkv"));
|
||||||
[InlineData(@"C:\cd\sps\ffffff.mkv")]
|
Assert.True(ignore.IsIgnored("/cd/sps/ffffff.mkv"));
|
||||||
[InlineData(@"D:\media\sps\movie.mkv")]
|
Assert.True(!ignore.IsIgnored("f:/cd/sps/thebestshot.mkv"));
|
||||||
public void CheckIgnoreRules_WithWindowsPaths_WithoutNormalization_DoesNotMatch(string path)
|
Assert.True(!ignore.IsIgnored("cd/sps/thebestshot.mkv"));
|
||||||
{
|
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