Compare commits
181 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 761a4e8415 | |||
| f0028c728f | |||
| fb0f3c3a76 | |||
| 2f92ee374a | |||
| bb377b1466 | |||
| 4a28f46cac | |||
| 8868b34d78 | |||
| 01a1209f0e | |||
| 5a65bc1e69 | |||
| 8ae5316198 | |||
| 1fbe1266e2 | |||
| 412ae7f4d2 | |||
| 26001fca93 | |||
| 8579c8f9ce | |||
| fcfc774da1 | |||
| 14c072dd32 | |||
| 132440c683 | |||
| 7b1314aff5 | |||
| c5e42ddcc6 | |||
| 3de86ffdb4 | |||
| 4b2c40f717 | |||
| 4c88bf3fe3 | |||
| 57d5423d64 | |||
| efa76c0b63 | |||
| 3f09fb8d70 | |||
| 1d19a5be61 | |||
| a7a6a22109 | |||
| 97508c6f42 | |||
| 296ba26b19 | |||
| c3523e7cf7 | |||
| c50c9c3dbf | |||
| a90735bc5a | |||
| 37a04d5dbf | |||
| e1f7f1405e | |||
| 6985a4f255 | |||
| 53c16c2342 | |||
| c1c77c8762 | |||
| 6bd108877e | |||
| 994101fcf4 | |||
| 1cfd11b98c | |||
| 2b02b53fc0 | |||
| 996500b2f8 | |||
| ce1c36dbf2 | |||
| 04040b25e0 | |||
| 66912deb84 | |||
| ac06022e0f | |||
| c2d99dc3f0 | |||
| ca6211ad61 | |||
| 5726535a26 | |||
| 6f85e30475 | |||
| a236f52c31 | |||
| 64652b6392 | |||
| 958a4f509c | |||
| 40045d2147 | |||
| 65971eb27e | |||
| 15dd23e4da | |||
| 1c47211a9e | |||
| 82e6a21f3b | |||
| f03e77a4d5 | |||
| 5c69d110cc | |||
| 3906343c91 | |||
| 0ee43f897b | |||
| 4dfb7b18ae | |||
| 666e95e27f | |||
| 195831ad4a | |||
| 892b05c5e6 | |||
| 0f52896691 | |||
| 83859a1e6d | |||
| 8d8e113771 | |||
| d95c281142 | |||
| b217f84d50 | |||
| b4bf5af7c8 | |||
| c8bd676b14 | |||
| 3c69283e2c | |||
| 07b9ba2bb4 | |||
| 2491dd513c | |||
| d3d9311f48 | |||
| 17264a6020 | |||
| b64d9bcd40 | |||
| 44dc647adb | |||
| 0cd817bba3 | |||
| 1e93c6ae30 | |||
| 13668a6ecb | |||
| c8eba90c17 | |||
| 6c76d30538 | |||
| a870f4ce70 | |||
| e3e6953c6d | |||
| 564990964d | |||
| f91839dd8c | |||
| c0bab5c173 | |||
| 5aadf8c291 | |||
| 654bd6fff1 | |||
| 70b9f9bf56 | |||
| ad7ed95b98 | |||
| 924c6682b9 | |||
| 5c5d49ee60 | |||
| 4279f13c67 | |||
| 4fc0521d69 | |||
| 3398f7f953 | |||
| 416894008e | |||
| 45ceba7ad1 | |||
| 6822693bd6 | |||
| 0187500373 | |||
| 869d537aaa | |||
| f444e93a56 | |||
| 149c77d9b1 | |||
| 2b283d249f | |||
| 2c42d75288 | |||
| a9c38870f9 | |||
| 77634d3b52 | |||
| eec4293e6f | |||
| 63eeb73608 | |||
| 7fcf01235c | |||
| 104e36f2f9 | |||
| 3d858955b6 | |||
| bbf40d6be2 | |||
| 7da6bd905a | |||
| 838225e962 | |||
| 4a5e8b99a0 | |||
| 5529625025 | |||
| 967fd66ca9 | |||
| a229526454 | |||
| 0a14279e2a | |||
| 76eeb8f655 | |||
| 6881cf6874 | |||
| e8efc433fa | |||
| 98587da53d | |||
| 09909d4df2 | |||
| 2372931b13 | |||
| 8e046ce22b | |||
| baafa10e87 | |||
| b478b115e3 | |||
| 022e2d9f97 | |||
| 0fbd8d85c8 | |||
| 080b02cc4c | |||
| 7b89e0e3a5 | |||
| 10a173c011 | |||
| a22c57ff33 | |||
| 2f6437a987 | |||
| f4844c08a5 | |||
| 73201ed498 | |||
| bcb1c9b652 | |||
| 0e584f6840 | |||
| 91204fc9f0 | |||
| c534c45033 | |||
| 71ed47a5d3 | |||
| 4885f5e6c9 | |||
| a6357f89ab | |||
| f78f1e834c | |||
| e6c9add45a | |||
| ea439c5ccf | |||
| 4f51a22081 | |||
| 153e920239 | |||
| be9663ae89 | |||
| d7b2fa62a3 | |||
| ffe5ae8056 | |||
| 711db363aa | |||
| 957c5ee061 | |||
| 37e388dec1 | |||
| 0c9b64de4b | |||
| 2cd1c70e55 | |||
| 2888567ea5 | |||
| 417a7011c7 | |||
| 3c8abeda7d | |||
| 7ff52bf755 | |||
| 7936ea59eb | |||
| 2e01fb3cda | |||
| 2fbc1190bc | |||
| a5aabbb885 | |||
| 975d1537d4 | |||
| b4ab75cd69 | |||
| a49ded84c6 | |||
| 2182640519 | |||
| f8cfc55a36 | |||
| 851f610e11 | |||
| 78e97dbaa9 | |||
| f30bbef781 | |||
| 2b85f43cab | |||
| 5741fa7dfa | |||
| 1d6224c9c6 | |||
| f2817fef74 |
@@ -34,7 +34,6 @@ jobs:
|
|||||||
inputs:
|
inputs:
|
||||||
packageType: sdk
|
packageType: sdk
|
||||||
version: ${{ parameters.DotNetSdkVersion }}
|
version: ${{ parameters.DotNetSdkVersion }}
|
||||||
includePreviewVersions: true
|
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2
|
||||||
displayName: 'Install ABI CompatibilityChecker Tool'
|
displayName: 'Install ABI CompatibilityChecker Tool'
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ jobs:
|
|||||||
inputs:
|
inputs:
|
||||||
packageType: sdk
|
packageType: sdk
|
||||||
version: ${{ parameters.DotNetSdkVersion }}
|
version: ${{ parameters.DotNetSdkVersion }}
|
||||||
includePreviewVersions: true
|
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2
|
||||||
displayName: 'Publish Server'
|
displayName: 'Publish Server'
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ jobs:
|
|||||||
inputs:
|
inputs:
|
||||||
sshEndpoint: repository
|
sshEndpoint: repository
|
||||||
runOptions: 'commands'
|
runOptions: 'commands'
|
||||||
commands: nohup sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) &
|
commands: nohup sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) $(Build.SourceBranch) &
|
||||||
|
|
||||||
- job: PublishNuget
|
- job: PublishNuget
|
||||||
displayName: 'Publish NuGet packages'
|
displayName: 'Publish NuGet packages'
|
||||||
@@ -199,7 +199,6 @@ jobs:
|
|||||||
inputs:
|
inputs:
|
||||||
packageType: 'sdk'
|
packageType: 'sdk'
|
||||||
version: '6.0.x'
|
version: '6.0.x'
|
||||||
includePreviewVersions: true
|
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2
|
||||||
displayName: 'Build Stable Nuget packages'
|
displayName: 'Build Stable Nuget packages'
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ jobs:
|
|||||||
inputs:
|
inputs:
|
||||||
packageType: sdk
|
packageType: sdk
|
||||||
version: ${{ parameters.DotNetSdkVersion }}
|
version: ${{ parameters.DotNetSdkVersion }}
|
||||||
includePreviewVersions: true
|
|
||||||
|
|
||||||
- task: SonarCloudPrepare@1
|
- task: SonarCloudPrepare@1
|
||||||
displayName: 'Prepare analysis on SonarCloud'
|
displayName: 'Prepare analysis on SonarCloud'
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a bug report
|
|
||||||
title: ''
|
|
||||||
labels: bug
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
<!-- A clear and concise description of what the bug is. -->
|
|
||||||
|
|
||||||
**System (please complete the following information):**
|
|
||||||
- OS: [e.g. Debian, Windows]
|
|
||||||
- Virtualization: [e.g. Docker, KVM, LXC]
|
|
||||||
- Clients: [Browser, Android, Fire Stick, etc.]
|
|
||||||
- Browser: [e.g. Firefox 91, Chrome 93, Safari 13]
|
|
||||||
- Jellyfin Version: [e.g. 10.7.6, unstable 20191231]
|
|
||||||
- FFmpeg Version: [e.g. 4.3.2-Jellyfin]
|
|
||||||
- Playback: [Direct Play, Remux, Direct Stream, Transcode]
|
|
||||||
- Hardware Acceleration: [e.g. none, VAAPI, NVENC, etc.]
|
|
||||||
- Installed Plugins: [e.g. none, Fanart, Anime, etc.]
|
|
||||||
- Reverse Proxy: [e.g. none, nginx, apache, etc.]
|
|
||||||
- Base URL: [e.g. none, yes: /example]
|
|
||||||
- Networking: [e.g. Host, Bridge/NAT]
|
|
||||||
- Storage: [e.g. local, NFS, cloud]
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
<!-- Steps to reproduce the behavior: -->
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
<!-- A clear and concise description of what you expected to happen. -->
|
|
||||||
|
|
||||||
**Server Logs**
|
|
||||||
<!-- Please paste any log errors. -->
|
|
||||||
|
|
||||||
**FFmpeg Logs**
|
|
||||||
<!-- Please paste any log errors. -->
|
|
||||||
|
|
||||||
**Browser Console Logs**
|
|
||||||
<!-- Please paste any log errors. -->
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
<!-- Add any other context about the problem here. -->
|
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
name: Issue Report
|
||||||
|
description: File an issue report
|
||||||
|
title: "[Issue]: "
|
||||||
|
labels: [bug, triage]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this bug report! Please provide as much detail as necessary, most questions may not be applicable to you. If you need real-time help, join us on [Matrix](https://matrix.to/#/#jellyfin-troubleshooting:matrix.org) or [Discord](https://discord.gg/zHBxVSXdBV).
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: Please describe your bug
|
||||||
|
description: Also tell us, what did you expect to happen?
|
||||||
|
placeholder: |
|
||||||
|
The more information that you are able to provide, the better. Did you do anything before this happened? Did you upgrade or change anything? Any screenshots or logs you can provide will be helpful.
|
||||||
|
|
||||||
|
This is my issue.
|
||||||
|
|
||||||
|
Steps to Reproduce
|
||||||
|
1. In this environment...
|
||||||
|
2. With this config...
|
||||||
|
3. Run '...'
|
||||||
|
4. See error...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Jellyfin Version
|
||||||
|
description: What version of Jellyfin are you running?
|
||||||
|
options:
|
||||||
|
- 10.7.7
|
||||||
|
- 10.7.z
|
||||||
|
- 10.6.4
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: version-other
|
||||||
|
attributes:
|
||||||
|
label: "if other:"
|
||||||
|
placeholder: Other
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Environment
|
||||||
|
description: |
|
||||||
|
Examples:
|
||||||
|
- **OS**: [e.g. Debian, Windows]
|
||||||
|
- **Virtualization**: [e.g. Docker, KVM, LXC]
|
||||||
|
- **Clients**: [Browser, Android, Fire Stick, etc.]
|
||||||
|
- **Browser**: [e.g. Firefox 91, Chrome 93, Safari 13]
|
||||||
|
- **FFmpeg Version**: [e.g. 4.3.2-Jellyfin]
|
||||||
|
- **Playback**: [Direct Play, Remux, Direct Stream, Transcode]
|
||||||
|
- **Hardware Acceleration**: [e.g. none, VAAPI, NVENC, etc.]
|
||||||
|
- **Installed Plugins**: [e.g. none, Fanart, Anime, etc.]
|
||||||
|
- **Reverse Proxy**: [e.g. none, nginx, apache, etc.]
|
||||||
|
- **Base URL**: [e.g. none, yes: /example]
|
||||||
|
- **Networking**: [e.g. Host, Bridge/NAT]
|
||||||
|
- **Storage**: [e.g. local, NFS, cloud]
|
||||||
|
value: |
|
||||||
|
- OS:
|
||||||
|
- Virtualization:
|
||||||
|
- Clients:
|
||||||
|
- Browser:
|
||||||
|
- FFmpeg Version:
|
||||||
|
- Playback Method:
|
||||||
|
- Hardware Acceleration:
|
||||||
|
- Plugins:
|
||||||
|
- Reverse Proxy:
|
||||||
|
- Base URL:
|
||||||
|
- Networking:
|
||||||
|
- Storage:
|
||||||
|
render: markdown
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Jellyfin logs
|
||||||
|
description: Please copy and paste any relevant log output. This can be found in Dashboard > Logs.
|
||||||
|
placeholder: For playback issues, browser/client and FFmpeg logs may be more useful.
|
||||||
|
render: shell
|
||||||
|
- type: textarea
|
||||||
|
id: ffmpeg-logs
|
||||||
|
attributes:
|
||||||
|
label: FFmpeg logs
|
||||||
|
description: Please copy and paste any relevant log output. This can be found in Dashboard > Logs.
|
||||||
|
placeholder: It's important to include the specific codec details. If no FFmpeg logs appear, the file was Direct Played and did not use FFmpeg.
|
||||||
|
render: shell
|
||||||
|
- type: textarea
|
||||||
|
id: browserlogs
|
||||||
|
attributes:
|
||||||
|
label: Please attach any browser or client logs here
|
||||||
|
placeholder: Access browser logs by using the F12 to bring up the console. Screenshots are typically easier to read than raw logs. For clients such as Android or iOS, please see our documentation.
|
||||||
|
- type: textarea
|
||||||
|
id: screenshots
|
||||||
|
attributes:
|
||||||
|
label: Please attach any screenshots here
|
||||||
|
placeholder: Images can be pasted directly into the textbox and will be hosted by github.
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: Code of Conduct
|
||||||
|
description: By submitting this issue, you agree to follow our [Code of Conduct](https://jellyfin.org/docs/general/community-standards.html#code-of-conduct)
|
||||||
|
options:
|
||||||
|
- label: I agree to follow this project's Code of Conduct
|
||||||
|
required: true
|
||||||
@@ -25,8 +25,7 @@ jobs:
|
|||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v1
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
include-prerelease: true
|
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
name: OpenAPI
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request_target:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
openapi-head:
|
||||||
|
name: OpenAPI - HEAD
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions: read-all
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
|
- name: Setup .NET Core
|
||||||
|
uses: actions/setup-dotnet@v1
|
||||||
|
with:
|
||||||
|
dotnet-version: '6.0.x'
|
||||||
|
- name: Generate openapi.json
|
||||||
|
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||||
|
- name: Upload openapi.json
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: openapi-head
|
||||||
|
retention-days: 14
|
||||||
|
if-no-files-found: error
|
||||||
|
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net6.0/openapi.json
|
||||||
|
|
||||||
|
openapi-base:
|
||||||
|
name: OpenAPI - BASE
|
||||||
|
if: ${{ github.base_ref != '' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions: read-all
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ github.base_ref }}
|
||||||
|
- name: Setup .NET Core
|
||||||
|
uses: actions/setup-dotnet@v1
|
||||||
|
with:
|
||||||
|
dotnet-version: '6.0.x'
|
||||||
|
- name: Generate openapi.json
|
||||||
|
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||||
|
- name: Upload openapi.json
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: openapi-base
|
||||||
|
retention-days: 14
|
||||||
|
if-no-files-found: error
|
||||||
|
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net6.0/openapi.json
|
||||||
|
|
||||||
|
openapi-diff:
|
||||||
|
name: OpenAPI - Difference
|
||||||
|
if: ${{ github.event_name == 'pull_request_target' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- openapi-head
|
||||||
|
- openapi-base
|
||||||
|
steps:
|
||||||
|
- name: Download openapi-head
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: openapi-head
|
||||||
|
path: openapi-head
|
||||||
|
- name: Download openapi-base
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: openapi-base
|
||||||
|
path: openapi-base
|
||||||
|
- name: Workaround openapi-diff issue
|
||||||
|
run: |
|
||||||
|
sed -i 's/"allOf"/"oneOf"/g' openapi-head/openapi.json
|
||||||
|
sed -i 's/"allOf"/"oneOf"/g' openapi-base/openapi.json
|
||||||
|
- name: Calculate OpenAPI difference
|
||||||
|
uses: docker://openapitools/openapi-diff
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
args: --fail-on-changed --markdown openapi-changes.md openapi-base/openapi.json openapi-head/openapi.json
|
||||||
|
- id: read-diff
|
||||||
|
name: Read openapi-diff output
|
||||||
|
run: |
|
||||||
|
body=$(cat openapi-changes.md)
|
||||||
|
body="${body//'%'/'%25'}"
|
||||||
|
body="${body//$'\n'/'%0A'}"
|
||||||
|
body="${body//$'\r'/'%0D'}"
|
||||||
|
echo ::set-output name=body::$body
|
||||||
|
- name: Find difference comment
|
||||||
|
uses: peter-evans/find-comment@v1
|
||||||
|
id: find-comment
|
||||||
|
with:
|
||||||
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
direction: last
|
||||||
|
body-includes: openapi-diff-workflow-comment
|
||||||
|
- name: Reply or edit difference comment (changed)
|
||||||
|
uses: peter-evans/create-or-update-comment@v1.4.5
|
||||||
|
if: ${{ steps.read-diff.outputs.body != '' }}
|
||||||
|
with:
|
||||||
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||||
|
edit-mode: replace
|
||||||
|
body: |
|
||||||
|
<!--openapi-diff-workflow-comment-->
|
||||||
|
<details>
|
||||||
|
<summary>Changes in OpenAPI specification found. Expand to see details.</summary>
|
||||||
|
|
||||||
|
${{ steps.read-diff.outputs.body }}
|
||||||
|
|
||||||
|
</details>
|
||||||
|
- name: Edit difference comment (unchanged)
|
||||||
|
uses: peter-evans/create-or-update-comment@v1.4.5
|
||||||
|
if: ${{ steps.read-diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }}
|
||||||
|
with:
|
||||||
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||||
|
edit-mode: replace
|
||||||
|
body: |
|
||||||
|
<!--openapi-diff-workflow-comment-->
|
||||||
|
|
||||||
|
No changes to OpenAPI specification found. See history of this comment for previous changes.
|
||||||
@@ -3,10 +3,13 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
|
||||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
|
||||||
namespace Emby.Dlna.ContentDirectory
|
namespace Emby.Dlna.ContentDirectory
|
||||||
@@ -13,24 +11,29 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
/// Initializes a new instance of the <see cref="ServerItem"/> class.
|
/// Initializes a new instance of the <see cref="ServerItem"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="item">The <see cref="BaseItem"/>.</param>
|
/// <param name="item">The <see cref="BaseItem"/>.</param>
|
||||||
public ServerItem(BaseItem item)
|
/// <param name="stubType">The stub type.</param>
|
||||||
|
public ServerItem(BaseItem item, StubType? stubType)
|
||||||
{
|
{
|
||||||
Item = item;
|
Item = item;
|
||||||
|
|
||||||
if (item is IItemByName && item is not Folder)
|
if (stubType.HasValue)
|
||||||
|
{
|
||||||
|
StubType = stubType;
|
||||||
|
}
|
||||||
|
else if (item is IItemByName and not Folder)
|
||||||
{
|
{
|
||||||
StubType = Dlna.ContentDirectory.StubType.Folder;
|
StubType = Dlna.ContentDirectory.StubType.Folder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the underlying base item.
|
/// Gets the underlying base item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BaseItem Item { get; set; }
|
public BaseItem Item { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the DLNA item type.
|
/// Gets the DLNA item type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StubType? StubType { get; set; }
|
public StubType? StubType { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -729,7 +729,7 @@ namespace Emby.Dlna.Didl
|
|||||||
{
|
{
|
||||||
if (item.PremiereDate.HasValue)
|
if (item.PremiereDate.HasValue)
|
||||||
{
|
{
|
||||||
AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o", CultureInfo.InvariantCulture), NsDc);
|
AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), NsDc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ namespace Emby.Dlna
|
|||||||
|
|
||||||
if (profile == null)
|
if (profile == null)
|
||||||
{
|
{
|
||||||
LogUnmatchedProfile(deviceInfo);
|
_logger.LogInformation("No matching device profile found. The default will need to be used. \n{@Profile}", deviceInfo);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -122,23 +122,6 @@ namespace Emby.Dlna
|
|||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogUnmatchedProfile(DeviceIdentification profile)
|
|
||||||
{
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
|
|
||||||
builder.AppendLine("No matching device profile found. The default will need to be used.");
|
|
||||||
builder.Append("FriendlyName: ").AppendLine(profile.FriendlyName);
|
|
||||||
builder.Append("Manufacturer: ").AppendLine(profile.Manufacturer);
|
|
||||||
builder.Append("ManufacturerUrl: ").AppendLine(profile.ManufacturerUrl);
|
|
||||||
builder.Append("ModelDescription: ").AppendLine(profile.ModelDescription);
|
|
||||||
builder.Append("ModelName: ").AppendLine(profile.ModelName);
|
|
||||||
builder.Append("ModelNumber: ").AppendLine(profile.ModelNumber);
|
|
||||||
builder.Append("ModelUrl: ").AppendLine(profile.ModelUrl);
|
|
||||||
builder.Append("SerialNumber: ").AppendLine(profile.SerialNumber);
|
|
||||||
|
|
||||||
_logger.LogInformation(builder.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to match a device with a profile.
|
/// Attempts to match a device with a profile.
|
||||||
/// Rules:
|
/// Rules:
|
||||||
@@ -416,7 +399,7 @@ namespace Emby.Dlna
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void UpdateProfile(DeviceProfile profile)
|
public void UpdateProfile(string profileId, DeviceProfile profile)
|
||||||
{
|
{
|
||||||
profile = ReserializeProfile(profile);
|
profile = ReserializeProfile(profile);
|
||||||
|
|
||||||
@@ -430,7 +413,7 @@ namespace Emby.Dlna
|
|||||||
throw new ArgumentException("Profile is missing Name");
|
throw new ArgumentException("Profile is missing Name");
|
||||||
}
|
}
|
||||||
|
|
||||||
var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profile.Id, StringComparison.OrdinalIgnoreCase));
|
var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profileId, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
|
var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
|
||||||
var path = Path.Combine(UserProfilesPath, newFilename);
|
var path = Path.Combine(UserProfilesPath, newFilename);
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ namespace Emby.Dlna.Main
|
|||||||
private readonly ISocketFactory _socketFactory;
|
private readonly ISocketFactory _socketFactory;
|
||||||
private readonly INetworkManager _networkManager;
|
private readonly INetworkManager _networkManager;
|
||||||
private readonly object _syncLock = new object();
|
private readonly object _syncLock = new object();
|
||||||
private readonly NetworkConfiguration _netConfig;
|
|
||||||
private readonly bool _disabled;
|
private readonly bool _disabled;
|
||||||
|
|
||||||
private PlayToManager _manager;
|
private PlayToManager _manager;
|
||||||
@@ -125,8 +124,8 @@ namespace Emby.Dlna.Main
|
|||||||
config);
|
config);
|
||||||
Current = this;
|
Current = this;
|
||||||
|
|
||||||
_netConfig = config.GetConfiguration<NetworkConfiguration>("network");
|
var netConfig = config.GetConfiguration<NetworkConfiguration>("network");
|
||||||
_disabled = appHost.ListenWithHttps && _netConfig.RequireHttps;
|
_disabled = appHost.ListenWithHttps && netConfig.RequireHttps;
|
||||||
|
|
||||||
if (_disabled && _config.GetDlnaConfiguration().EnableServer)
|
if (_disabled && _config.GetDlnaConfiguration().EnableServer)
|
||||||
{
|
{
|
||||||
@@ -219,11 +218,6 @@ namespace Emby.Dlna.Main
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogMessage(string msg)
|
|
||||||
{
|
|
||||||
_logger.LogDebug(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartDeviceDiscovery(ISsdpCommunicationsServer communicationsServer)
|
private void StartDeviceDiscovery(ISsdpCommunicationsServer communicationsServer)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -273,7 +267,7 @@ namespace Emby.Dlna.Main
|
|||||||
Environment.OSVersion.VersionString,
|
Environment.OSVersion.VersionString,
|
||||||
_config.GetDlnaConfiguration().SendOnlyMatchedHost)
|
_config.GetDlnaConfiguration().SendOnlyMatchedHost)
|
||||||
{
|
{
|
||||||
LogFunction = LogMessage,
|
LogFunction = (msg) => _logger.LogDebug("{Msg}", msg),
|
||||||
SupportPnpRootDevice = false
|
SupportPnpRootDevice = false
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -318,15 +312,9 @@ namespace Emby.Dlna.Main
|
|||||||
|
|
||||||
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
|
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
|
||||||
|
|
||||||
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
|
_logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress}", fullService, address);
|
||||||
|
|
||||||
var uri = new UriBuilder(_appHost.GetSmartApiUrl(address.Address) + descriptorUri);
|
var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(false) + descriptorUri);
|
||||||
if (!string.IsNullOrEmpty(_appHost.PublishedServerUrl))
|
|
||||||
{
|
|
||||||
// DLNA will only work over http, so we must reset to http:// : {port}.
|
|
||||||
uri.Scheme = "http";
|
|
||||||
uri.Port = _netConfig.HttpServerPortNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
var device = new SsdpRootDevice
|
var device = new SsdpRootDevice
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ namespace Emby.Dlna.Service
|
|||||||
requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false);
|
requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogDebug("Received control request {0}", requestInfo.LocalName);
|
Logger.LogDebug("Received control request {LocalName}, params: {@Headers}", requestInfo.LocalName, requestInfo.Headers);
|
||||||
|
|
||||||
var settings = new XmlWriterSettings
|
var settings = new XmlWriterSettings
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Emby.Drawing
|
|||||||
public sealed class ImageProcessor : IImageProcessor, IDisposable
|
public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||||
{
|
{
|
||||||
// Increment this when there's a change requiring caches to be invalidated
|
// Increment this when there's a change requiring caches to be invalidated
|
||||||
private const string Version = "3";
|
private const char Version = '3';
|
||||||
|
|
||||||
private static readonly HashSet<string> _transparentImageTypes
|
private static readonly HashSet<string> _transparentImageTypes
|
||||||
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
|
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#pragma warning disable CA1819
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
@@ -253,6 +255,8 @@ namespace Emby.Naming.Common
|
|||||||
},
|
},
|
||||||
// <!-- foo.ep01, foo.EP_01 -->
|
// <!-- foo.ep01, foo.EP_01 -->
|
||||||
new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"),
|
new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"),
|
||||||
|
// <!-- foo.E01., foo.e01. -->
|
||||||
|
new EpisodeExpression(@"[^\\/]*?()\.?[Ee]([0-9]+)\.([^\\/]*)$"),
|
||||||
new EpisodeExpression("(?<year>[0-9]{4})[\\.-](?<month>[0-9]{2})[\\.-](?<day>[0-9]{2})", true)
|
new EpisodeExpression("(?<year>[0-9]{4})[\\.-](?<month>[0-9]{2})[\\.-](?<day>[0-9]{2})", true)
|
||||||
{
|
{
|
||||||
DateTimeFormats = new[]
|
DateTimeFormats = new[]
|
||||||
@@ -371,6 +375,20 @@ namespace Emby.Naming.Common
|
|||||||
IsOptimistic = true,
|
IsOptimistic = true,
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Series and season only expression
|
||||||
|
// "the show/season 1", "the show/s01"
|
||||||
|
new EpisodeExpression(@"(.*(\\|\/))*(?<seriesname>.+)\/[Ss](eason)?[\. _\-]*(?<seasonnumber>[0-9]+)")
|
||||||
|
{
|
||||||
|
IsNamed = true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Series and season only expression
|
||||||
|
// "the show S01", "the show season 1"
|
||||||
|
new EpisodeExpression(@"(.*(\\|\/))*(?<seriesname>.+)[\. _\-]+[sS](eason)?[\. _\-]*(?<seasonnumber>[0-9]+)")
|
||||||
|
{
|
||||||
|
IsNamed = true
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
EpisodeWithoutSeasonExpressions = new[]
|
EpisodeWithoutSeasonExpressions = new[]
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers-->
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
namespace Emby.Naming.TV
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Holder object for Series information.
|
||||||
|
/// </summary>
|
||||||
|
public class SeriesInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="SeriesInfo"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Path to the file.</param>
|
||||||
|
public SeriesInfo(string path)
|
||||||
|
{
|
||||||
|
Path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The path.</value>
|
||||||
|
public string Path { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the series.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name of the series.</value>
|
||||||
|
public string? Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using Emby.Naming.Common;
|
||||||
|
|
||||||
|
namespace Emby.Naming.TV
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to parse information about series from paths containing more information that only the series name.
|
||||||
|
/// Uses the same regular expressions as the EpisodePathParser but have different success criteria.
|
||||||
|
/// </summary>
|
||||||
|
public static class SeriesPathParser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Parses information about series from path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options"><see cref="NamingOptions"/> object containing EpisodeExpressions and MultipleEpisodeExpressions.</param>
|
||||||
|
/// <param name="path">Path.</param>
|
||||||
|
/// <returns>Returns <see cref="SeriesPathParserResult"/> object.</returns>
|
||||||
|
public static SeriesPathParserResult Parse(NamingOptions options, string path)
|
||||||
|
{
|
||||||
|
SeriesPathParserResult? result = null;
|
||||||
|
|
||||||
|
foreach (var expression in options.EpisodeExpressions)
|
||||||
|
{
|
||||||
|
var currentResult = Parse(path, expression);
|
||||||
|
if (currentResult.Success)
|
||||||
|
{
|
||||||
|
result = currentResult;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(result.SeriesName))
|
||||||
|
{
|
||||||
|
result.SeriesName = result.SeriesName.Trim(' ', '_', '.', '-');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result ?? new SeriesPathParserResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SeriesPathParserResult Parse(string name, EpisodeExpression expression)
|
||||||
|
{
|
||||||
|
var result = new SeriesPathParserResult();
|
||||||
|
|
||||||
|
var match = expression.Regex.Match(name);
|
||||||
|
|
||||||
|
if (match.Success && match.Groups.Count >= 3)
|
||||||
|
{
|
||||||
|
if (expression.IsNamed)
|
||||||
|
{
|
||||||
|
result.SeriesName = match.Groups["seriesname"].Value;
|
||||||
|
result.Success = !string.IsNullOrEmpty(result.SeriesName) && !string.IsNullOrEmpty(match.Groups["seasonnumber"]?.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
namespace Emby.Naming.TV
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Holder object for <see cref="SeriesPathParser"/> result.
|
||||||
|
/// </summary>
|
||||||
|
public class SeriesPathParserResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the series.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name of the series.</value>
|
||||||
|
public string? SeriesName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether parsing was successful.
|
||||||
|
/// </summary>
|
||||||
|
public bool Success { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Emby.Naming.Common;
|
||||||
|
|
||||||
|
namespace Emby.Naming.TV
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to resolve information about series from path.
|
||||||
|
/// </summary>
|
||||||
|
public static class SeriesResolver
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Regex that matches strings of at least 2 characters separated by a dot or underscore.
|
||||||
|
/// Used for removing separators between words, i.e turns "The_show" into "The show" while
|
||||||
|
/// preserving namings like "S.H.O.W".
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Regex _seriesNameRegex = new Regex(@"((?<a>[^\._]{2,})[\._]*)|([\._](?<b>[^\._]{2,}))");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolve information about series from path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options"><see cref="NamingOptions"/> object passed to <see cref="SeriesPathParser"/>.</param>
|
||||||
|
/// <param name="path">Path to series.</param>
|
||||||
|
/// <returns>SeriesInfo.</returns>
|
||||||
|
public static SeriesInfo Resolve(NamingOptions options, string path)
|
||||||
|
{
|
||||||
|
string seriesName = Path.GetFileName(path);
|
||||||
|
|
||||||
|
SeriesPathParserResult result = SeriesPathParser.Parse(options, path);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(result.SeriesName))
|
||||||
|
{
|
||||||
|
seriesName = result.SeriesName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(seriesName))
|
||||||
|
{
|
||||||
|
seriesName = _seriesNameRegex.Replace(seriesName, "${a} ${b}").Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SeriesInfo(path)
|
||||||
|
{
|
||||||
|
Name = seriesName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@@ -56,6 +57,7 @@ using MediaBrowser.Common.Updates;
|
|||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Chapters;
|
using MediaBrowser.Controller.Chapters;
|
||||||
|
using MediaBrowser.Controller.ClientEvent;
|
||||||
using MediaBrowser.Controller.Collections;
|
using MediaBrowser.Controller.Collections;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
@@ -117,7 +119,7 @@ namespace Emby.Server.Implementations
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The disposable parts.
|
/// The disposable parts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly List<IDisposable> _disposableParts = new List<IDisposable>();
|
private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new ();
|
||||||
|
|
||||||
private readonly IFileSystem _fileSystemManager;
|
private readonly IFileSystem _fileSystemManager;
|
||||||
private readonly IConfiguration _startupConfig;
|
private readonly IConfiguration _startupConfig;
|
||||||
@@ -128,7 +130,6 @@ namespace Emby.Server.Implementations
|
|||||||
private List<Type> _creatingInstances;
|
private List<Type> _creatingInstances;
|
||||||
private IMediaEncoder _mediaEncoder;
|
private IMediaEncoder _mediaEncoder;
|
||||||
private ISessionManager _sessionManager;
|
private ISessionManager _sessionManager;
|
||||||
private string[] _urlPrefixes;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets all concrete types.
|
/// Gets or sets all concrete types.
|
||||||
@@ -147,25 +148,20 @@ namespace Emby.Server.Implementations
|
|||||||
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
|
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
|
||||||
/// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param>
|
/// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param>
|
||||||
/// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param>
|
/// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param>
|
||||||
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
|
||||||
/// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param>
|
|
||||||
public ApplicationHost(
|
public ApplicationHost(
|
||||||
IServerApplicationPaths applicationPaths,
|
IServerApplicationPaths applicationPaths,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IStartupOptions options,
|
IStartupOptions options,
|
||||||
IConfiguration startupConfig,
|
IConfiguration startupConfig)
|
||||||
IFileSystem fileSystem,
|
|
||||||
IServiceCollection serviceCollection)
|
|
||||||
{
|
{
|
||||||
ApplicationPaths = applicationPaths;
|
ApplicationPaths = applicationPaths;
|
||||||
LoggerFactory = loggerFactory;
|
LoggerFactory = loggerFactory;
|
||||||
_startupOptions = options;
|
_startupOptions = options;
|
||||||
_startupConfig = startupConfig;
|
_startupConfig = startupConfig;
|
||||||
_fileSystemManager = fileSystem;
|
_fileSystemManager = new ManagedFileSystem(LoggerFactory.CreateLogger<ManagedFileSystem>(), applicationPaths);
|
||||||
ServiceCollection = serviceCollection;
|
|
||||||
|
|
||||||
Logger = LoggerFactory.CreateLogger<ApplicationHost>();
|
Logger = LoggerFactory.CreateLogger<ApplicationHost>();
|
||||||
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
|
_fileSystemManager.AddShortcutHandler(new MbLinkShortcutHandler(_fileSystemManager));
|
||||||
|
|
||||||
ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
|
ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
|
||||||
ApplicationVersionString = ApplicationVersion.ToString(3);
|
ApplicationVersionString = ApplicationVersion.ToString(3);
|
||||||
@@ -214,7 +210,7 @@ namespace Emby.Server.Implementations
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the <see cref="INetworkManager"/> singleton instance.
|
/// Gets the <see cref="INetworkManager"/> singleton instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public INetworkManager NetManager { get; internal set; }
|
public INetworkManager NetManager { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this instance has changes that require the entire application to restart.
|
/// Gets a value indicating whether this instance has changes that require the entire application to restart.
|
||||||
@@ -230,24 +226,22 @@ namespace Emby.Server.Implementations
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected ILogger<ApplicationHost> Logger { get; }
|
protected ILogger<ApplicationHost> Logger { get; }
|
||||||
|
|
||||||
protected IServiceCollection ServiceCollection { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the logger factory.
|
/// Gets the logger factory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected ILoggerFactory LoggerFactory { get; }
|
protected ILoggerFactory LoggerFactory { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the application paths.
|
/// Gets the application paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The application paths.</value>
|
/// <value>The application paths.</value>
|
||||||
protected IServerApplicationPaths ApplicationPaths { get; set; }
|
protected IServerApplicationPaths ApplicationPaths { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the configuration manager.
|
/// Gets the configuration manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The configuration manager.</value>
|
/// <value>The configuration manager.</value>
|
||||||
public ServerConfigurationManager ConfigurationManager { get; set; }
|
public ServerConfigurationManager ConfigurationManager { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the service provider.
|
/// Gets or sets the service provider.
|
||||||
@@ -350,22 +344,6 @@ namespace Emby.Server.Implementations
|
|||||||
.Replace(appPaths.InternalMetadataPath, appPaths.VirtualInternalMetadataPath, StringComparison.OrdinalIgnoreCase);
|
.Replace(appPaths.InternalMetadataPath, appPaths.VirtualInternalMetadataPath, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates an instance of type and resolves all constructor dependencies.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The type.</param>
|
|
||||||
/// <returns>System.Object.</returns>
|
|
||||||
public object CreateInstance(Type type)
|
|
||||||
=> ActivatorUtilities.CreateInstance(ServiceProvider, type);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates an instance of type and resolves all constructor dependencies.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type.</typeparam>
|
|
||||||
/// <returns>T.</returns>
|
|
||||||
public T CreateInstance<T>()
|
|
||||||
=> ActivatorUtilities.CreateInstance<T>(ServiceProvider);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the instance safe.
|
/// Creates the instance safe.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -375,7 +353,7 @@ namespace Emby.Server.Implementations
|
|||||||
{
|
{
|
||||||
_creatingInstances ??= new List<Type>();
|
_creatingInstances ??= new List<Type>();
|
||||||
|
|
||||||
if (_creatingInstances.IndexOf(type) != -1)
|
if (_creatingInstances.Contains(type))
|
||||||
{
|
{
|
||||||
Logger.LogError("DI Loop detected in the attempted creation of {Type}", type.FullName);
|
Logger.LogError("DI Loop detected in the attempted creation of {Type}", type.FullName);
|
||||||
foreach (var entry in _creatingInstances)
|
foreach (var entry in _creatingInstances)
|
||||||
@@ -385,7 +363,7 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
_pluginManager.FailPlugin(type.Assembly);
|
_pluginManager.FailPlugin(type.Assembly);
|
||||||
|
|
||||||
throw new ExternalException("DI Loop detected.");
|
throw new TypeLoadException("DI Loop detected");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -418,8 +396,15 @@ namespace Emby.Server.Implementations
|
|||||||
public IEnumerable<Type> GetExportTypes<T>()
|
public IEnumerable<Type> GetExportTypes<T>()
|
||||||
{
|
{
|
||||||
var currentType = typeof(T);
|
var currentType = typeof(T);
|
||||||
|
var numberOfConcreteTypes = _allConcreteTypes.Length;
|
||||||
return _allConcreteTypes.Where(i => currentType.IsAssignableFrom(i));
|
for (var i = 0; i < numberOfConcreteTypes; i++)
|
||||||
|
{
|
||||||
|
var type = _allConcreteTypes[i];
|
||||||
|
if (currentType.IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
yield return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -434,9 +419,9 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
if (manageLifetime)
|
if (manageLifetime)
|
||||||
{
|
{
|
||||||
lock (_disposableParts)
|
foreach (var part in parts.OfType<IDisposable>())
|
||||||
{
|
{
|
||||||
_disposableParts.AddRange(parts.OfType<IDisposable>());
|
_disposableParts.TryAdd(part, byte.MinValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,9 +440,9 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
if (manageLifetime)
|
if (manageLifetime)
|
||||||
{
|
{
|
||||||
lock (_disposableParts)
|
foreach (var part in parts.OfType<IDisposable>())
|
||||||
{
|
{
|
||||||
_disposableParts.AddRange(parts.OfType<IDisposable>());
|
_disposableParts.TryAdd(part, byte.MinValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,7 +506,7 @@ namespace Emby.Server.Implementations
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Init()
|
public void Init(IServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
DiscoverTypes();
|
DiscoverTypes();
|
||||||
|
|
||||||
@@ -551,128 +536,129 @@ namespace Emby.Server.Implementations
|
|||||||
CertificatePath = networkConfiguration.CertificatePath;
|
CertificatePath = networkConfiguration.CertificatePath;
|
||||||
Certificate = GetCertificate(CertificatePath, networkConfiguration.CertificatePassword);
|
Certificate = GetCertificate(CertificatePath, networkConfiguration.CertificatePassword);
|
||||||
|
|
||||||
RegisterServices();
|
RegisterServices(serviceCollection);
|
||||||
|
|
||||||
_pluginManager.RegisterServices(ServiceCollection);
|
_pluginManager.RegisterServices(serviceCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers services/resources with the service collection that will be available via DI.
|
/// Registers services/resources with the service collection that will be available via DI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void RegisterServices()
|
/// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param>
|
||||||
|
protected virtual void RegisterServices(IServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
ServiceCollection.AddSingleton(_startupOptions);
|
serviceCollection.AddSingleton(_startupOptions);
|
||||||
|
|
||||||
ServiceCollection.AddMemoryCache();
|
serviceCollection.AddMemoryCache();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IServerConfigurationManager>(ConfigurationManager);
|
serviceCollection.AddSingleton<IServerConfigurationManager>(ConfigurationManager);
|
||||||
ServiceCollection.AddSingleton<IConfigurationManager>(ConfigurationManager);
|
serviceCollection.AddSingleton<IConfigurationManager>(ConfigurationManager);
|
||||||
ServiceCollection.AddSingleton<IApplicationHost>(this);
|
serviceCollection.AddSingleton<IApplicationHost>(this);
|
||||||
ServiceCollection.AddSingleton<IPluginManager>(_pluginManager);
|
serviceCollection.AddSingleton(_pluginManager);
|
||||||
ServiceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
|
serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
|
||||||
|
|
||||||
ServiceCollection.AddSingleton(_fileSystemManager);
|
serviceCollection.AddSingleton(_fileSystemManager);
|
||||||
ServiceCollection.AddSingleton<TmdbClientManager>();
|
serviceCollection.AddSingleton<TmdbClientManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton(NetManager);
|
serviceCollection.AddSingleton(NetManager);
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ITaskManager, TaskManager>();
|
serviceCollection.AddSingleton<ITaskManager, TaskManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton(_xmlSerializer);
|
serviceCollection.AddSingleton(_xmlSerializer);
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IStreamHelper, StreamHelper>();
|
serviceCollection.AddSingleton<IStreamHelper, StreamHelper>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
|
serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ISocketFactory, SocketFactory>();
|
serviceCollection.AddSingleton<ISocketFactory, SocketFactory>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IInstallationManager, InstallationManager>();
|
serviceCollection.AddSingleton<IInstallationManager, InstallationManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IZipClient, ZipClient>();
|
serviceCollection.AddSingleton<IZipClient, ZipClient>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IServerApplicationHost>(this);
|
serviceCollection.AddSingleton<IServerApplicationHost>(this);
|
||||||
ServiceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
|
serviceCollection.AddSingleton(ApplicationPaths);
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ILocalizationManager, LocalizationManager>();
|
serviceCollection.AddSingleton<ILocalizationManager, LocalizationManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>();
|
serviceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
|
serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
|
||||||
ServiceCollection.AddSingleton<IUserDataManager, UserDataManager>();
|
serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
|
serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
|
serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
|
||||||
ServiceCollection.AddSingleton<EncodingHelper>();
|
serviceCollection.AddSingleton<EncodingHelper>();
|
||||||
|
|
||||||
// TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required
|
// TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required
|
||||||
ServiceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
|
serviceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
|
||||||
ServiceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>));
|
serviceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>));
|
||||||
ServiceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
|
serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
|
||||||
ServiceCollection.AddSingleton<ILibraryManager, LibraryManager>();
|
serviceCollection.AddSingleton<ILibraryManager, LibraryManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IMusicManager, MusicManager>();
|
serviceCollection.AddSingleton<IMusicManager, MusicManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>();
|
serviceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ISearchEngine, SearchEngine>();
|
serviceCollection.AddSingleton<ISearchEngine, SearchEngine>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IWebSocketManager, WebSocketManager>();
|
serviceCollection.AddSingleton<IWebSocketManager, WebSocketManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IImageProcessor, ImageProcessor>();
|
serviceCollection.AddSingleton<IImageProcessor, ImageProcessor>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>();
|
serviceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>();
|
serviceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ISubtitleManager, SubtitleManager>();
|
serviceCollection.AddSingleton<ISubtitleManager, SubtitleManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IProviderManager, ProviderManager>();
|
serviceCollection.AddSingleton<IProviderManager, ProviderManager>();
|
||||||
|
|
||||||
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
||||||
ServiceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>));
|
serviceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>));
|
||||||
ServiceCollection.AddSingleton<IDtoService, DtoService>();
|
serviceCollection.AddSingleton<IDtoService, DtoService>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IChannelManager, ChannelManager>();
|
serviceCollection.AddSingleton<IChannelManager, ChannelManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ISessionManager, SessionManager>();
|
serviceCollection.AddSingleton<ISessionManager, SessionManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IDlnaManager, DlnaManager>();
|
serviceCollection.AddSingleton<IDlnaManager, DlnaManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ICollectionManager, CollectionManager>();
|
serviceCollection.AddSingleton<ICollectionManager, CollectionManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
|
serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>();
|
serviceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<LiveTvDtoService>();
|
serviceCollection.AddSingleton<LiveTvDtoService>();
|
||||||
ServiceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
|
serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IUserViewManager, UserViewManager>();
|
serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<INotificationManager, NotificationManager>();
|
serviceCollection.AddSingleton<INotificationManager, NotificationManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
|
serviceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IChapterManager, ChapterManager>();
|
serviceCollection.AddSingleton<IChapterManager, ChapterManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
|
serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
|
||||||
|
|
||||||
ServiceCollection.AddScoped<ISessionContext, SessionContext>();
|
serviceCollection.AddScoped<ISessionContext, SessionContext>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IAuthService, AuthService>();
|
serviceCollection.AddSingleton<IAuthService, AuthService>();
|
||||||
ServiceCollection.AddSingleton<IQuickConnect, QuickConnectManager>();
|
serviceCollection.AddSingleton<IQuickConnect, QuickConnectManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
|
serviceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
|
serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<TranscodingJobHelper>();
|
serviceCollection.AddSingleton<TranscodingJobHelper>();
|
||||||
ServiceCollection.AddScoped<MediaInfoHelper>();
|
serviceCollection.AddScoped<MediaInfoHelper>();
|
||||||
ServiceCollection.AddScoped<AudioHelper>();
|
serviceCollection.AddScoped<AudioHelper>();
|
||||||
ServiceCollection.AddScoped<DynamicHlsHelper>();
|
serviceCollection.AddScoped<DynamicHlsHelper>();
|
||||||
|
serviceCollection.AddScoped<IClientEventLogger, ClientEventLogger>();
|
||||||
ServiceCollection.AddSingleton<IDirectoryService, DirectoryService>();
|
serviceCollection.AddSingleton<IDirectoryService, DirectoryService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -795,8 +781,6 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
_pluginManager.CreatePlugins();
|
_pluginManager.CreatePlugins();
|
||||||
|
|
||||||
_urlPrefixes = GetUrlPrefixes().ToArray();
|
|
||||||
|
|
||||||
Resolve<ILibraryManager>().AddParts(
|
Resolve<ILibraryManager>().AddParts(
|
||||||
GetExports<IResolverIgnoreRule>(),
|
GetExports<IResolverIgnoreRule>(),
|
||||||
GetExports<IItemResolver>(),
|
GetExports<IItemResolver>(),
|
||||||
@@ -864,32 +848,12 @@ namespace Emby.Server.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<string> GetUrlPrefixes()
|
|
||||||
{
|
|
||||||
var hosts = new[] { "+" };
|
|
||||||
|
|
||||||
return hosts.SelectMany(i =>
|
|
||||||
{
|
|
||||||
var prefixes = new List<string>
|
|
||||||
{
|
|
||||||
"http://" + i + ":" + HttpPort + "/"
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Certificate != null)
|
|
||||||
{
|
|
||||||
prefixes.Add("https://" + i + ":" + HttpsPort + "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
return prefixes;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when [configuration updated].
|
/// Called when [configuration updated].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The sender.</param>
|
/// <param name="sender">The sender.</param>
|
||||||
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
|
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
|
||||||
protected void OnConfigurationUpdated(object sender, EventArgs e)
|
private void OnConfigurationUpdated(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var requiresRestart = false;
|
var requiresRestart = false;
|
||||||
var networkConfiguration = ConfigurationManager.GetNetworkConfiguration();
|
var networkConfiguration = ConfigurationManager.GetNetworkConfiguration();
|
||||||
@@ -898,8 +862,8 @@ namespace Emby.Server.Implementations
|
|||||||
if (HttpPort != 0 && HttpsPort != 0)
|
if (HttpPort != 0 && HttpsPort != 0)
|
||||||
{
|
{
|
||||||
// Need to restart if ports have changed
|
// Need to restart if ports have changed
|
||||||
if (networkConfiguration.HttpServerPortNumber != HttpPort ||
|
if (networkConfiguration.HttpServerPortNumber != HttpPort
|
||||||
networkConfiguration.HttpsPortNumber != HttpsPort)
|
|| networkConfiguration.HttpsPortNumber != HttpsPort)
|
||||||
{
|
{
|
||||||
if (ConfigurationManager.Configuration.IsPortAuthorized)
|
if (ConfigurationManager.Configuration.IsPortAuthorized)
|
||||||
{
|
{
|
||||||
@@ -911,11 +875,6 @@ namespace Emby.Server.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_urlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
requiresRestart = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ValidateSslCertificate(networkConfiguration))
|
if (ValidateSslCertificate(networkConfiguration))
|
||||||
{
|
{
|
||||||
requiresRestart = true;
|
requiresRestart = true;
|
||||||
@@ -957,7 +916,7 @@ namespace Emby.Server.Implementations
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies that the kernel that a change has been made that requires a restart.
|
/// Notifies the kernel that a change has been made that requires a restart.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void NotifyPendingRestart()
|
public void NotifyPendingRestart()
|
||||||
{
|
{
|
||||||
@@ -1098,11 +1057,6 @@ namespace Emby.Server.Implementations
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo()
|
|
||||||
=> NetManager.GetMacAddresses()
|
|
||||||
.Select(i => new WakeOnLanInfo(i))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
public PublicSystemInfo GetPublicSystemInfo(HttpRequest request)
|
public PublicSystemInfo GetPublicSystemInfo(HttpRequest request)
|
||||||
{
|
{
|
||||||
return new PublicSystemInfo
|
return new PublicSystemInfo
|
||||||
@@ -1118,7 +1072,7 @@ namespace Emby.Server.Implementations
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string GetSmartApiUrl(IPAddress remoteAddr, int? port = null)
|
public string GetSmartApiUrl(IPAddress remoteAddr)
|
||||||
{
|
{
|
||||||
// Published server ends with a /
|
// Published server ends with a /
|
||||||
if (!string.IsNullOrEmpty(PublishedServerUrl))
|
if (!string.IsNullOrEmpty(PublishedServerUrl))
|
||||||
@@ -1127,18 +1081,12 @@ namespace Emby.Server.Implementations
|
|||||||
return PublishedServerUrl.Trim('/');
|
return PublishedServerUrl.Trim('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
string smart = NetManager.GetBindInterface(remoteAddr, out port);
|
string smart = NetManager.GetBindInterface(remoteAddr, out var port);
|
||||||
// If the smartAPI doesn't start with http then treat it as a host or ip.
|
|
||||||
if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return smart.Trim('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetLocalApiUrl(smart.Trim('/'), null, port);
|
return GetLocalApiUrl(smart.Trim('/'), null, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string GetSmartApiUrl(HttpRequest request, int? port = null)
|
public string GetSmartApiUrl(HttpRequest request)
|
||||||
{
|
{
|
||||||
// Return the host in the HTTP request as the API url
|
// Return the host in the HTTP request as the API url
|
||||||
if (ConfigurationManager.GetNetworkConfiguration().EnablePublishedServerUriByRequest)
|
if (ConfigurationManager.GetNetworkConfiguration().EnablePublishedServerUriByRequest)
|
||||||
@@ -1159,18 +1107,12 @@ namespace Emby.Server.Implementations
|
|||||||
return PublishedServerUrl.Trim('/');
|
return PublishedServerUrl.Trim('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
string smart = NetManager.GetBindInterface(request, out port);
|
string smart = NetManager.GetBindInterface(request, out var port);
|
||||||
// If the smartAPI doesn't start with http then treat it as a host or ip.
|
|
||||||
if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return smart.Trim('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetLocalApiUrl(smart.Trim('/'), request.Scheme, port);
|
return GetLocalApiUrl(smart.Trim('/'), request.Scheme, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string GetSmartApiUrl(string hostname, int? port = null)
|
public string GetSmartApiUrl(string hostname)
|
||||||
{
|
{
|
||||||
// Published server ends with a /
|
// Published server ends with a /
|
||||||
if (!string.IsNullOrEmpty(PublishedServerUrl))
|
if (!string.IsNullOrEmpty(PublishedServerUrl))
|
||||||
@@ -1179,31 +1121,29 @@ namespace Emby.Server.Implementations
|
|||||||
return PublishedServerUrl.Trim('/');
|
return PublishedServerUrl.Trim('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
string smart = NetManager.GetBindInterface(hostname, out port);
|
string smart = NetManager.GetBindInterface(hostname, out var port);
|
||||||
|
|
||||||
// If the smartAPI doesn't start with http then treat it as a host or ip.
|
|
||||||
if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return smart.Trim('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetLocalApiUrl(smart.Trim('/'), null, port);
|
return GetLocalApiUrl(smart.Trim('/'), null, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string GetLoopbackHttpApiUrl()
|
public string GetApiUrlForLocalAccess(bool allowHttps)
|
||||||
{
|
{
|
||||||
if (NetManager.IsIP6Enabled)
|
// With an empty source, the port will be null
|
||||||
{
|
string smart = NetManager.GetBindInterface(string.Empty, out _);
|
||||||
return GetLocalApiUrl("::1", Uri.UriSchemeHttp, HttpPort);
|
var scheme = allowHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
|
||||||
}
|
var port = allowHttps ? HttpsPort : HttpPort;
|
||||||
|
return GetLocalApiUrl(smart.Trim('/'), scheme, port);
|
||||||
return GetLocalApiUrl("127.0.0.1", Uri.UriSchemeHttp, HttpPort);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string GetLocalApiUrl(string hostname, string scheme = null, int? port = null)
|
public string GetLocalApiUrl(string hostname, string scheme = null, int? port = null)
|
||||||
{
|
{
|
||||||
|
// If the smartAPI doesn't start with http then treat it as a host or ip.
|
||||||
|
if (hostname.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return hostname.TrimEnd('/');
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does
|
// NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does
|
||||||
// not. For consistency, always trim the trailing slash.
|
// not. For consistency, always trim the trailing slash.
|
||||||
return new UriBuilder
|
return new UriBuilder
|
||||||
@@ -1277,12 +1217,15 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
Logger.LogInformation("Disposing {Type}", type.Name);
|
Logger.LogInformation("Disposing {Type}", type.Name);
|
||||||
|
|
||||||
var parts = _disposableParts.Distinct().Where(i => i.GetType() != type).ToList();
|
foreach (var (part, _) in _disposableParts)
|
||||||
_disposableParts.Clear();
|
|
||||||
|
|
||||||
foreach (var part in parts)
|
|
||||||
{
|
{
|
||||||
Logger.LogInformation("Disposing {Type}", part.GetType().Name);
|
var partType = part.GetType();
|
||||||
|
if (partType == type)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogInformation("Disposing {Type}", partType.Name);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1290,9 +1233,11 @@ namespace Emby.Server.Implementations
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Error disposing {Type}", part.GetType().Name);
|
Logger.LogError(ex, "Error disposing {Type}", partType.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_disposableParts.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|||||||
@@ -134,14 +134,11 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
var dto = GetBaseItemDtoInternal(item, options, user, owner);
|
var dto = GetBaseItemDtoInternal(item, options, user, owner);
|
||||||
if (item is LiveTvChannel tvChannel)
|
if (item is LiveTvChannel tvChannel)
|
||||||
{
|
{
|
||||||
var list = new List<(BaseItemDto, LiveTvChannel)>(1) { (dto, tvChannel) };
|
LivetvManager.AddChannelInfo(new[] { (dto, tvChannel) }, options, user);
|
||||||
LivetvManager.AddChannelInfo(list, options, user);
|
|
||||||
}
|
}
|
||||||
else if (item is LiveTvProgram)
|
else if (item is LiveTvProgram)
|
||||||
{
|
{
|
||||||
var list = new List<(BaseItem, BaseItemDto)>(1) { (item, dto) };
|
LivetvManager.AddInfoToProgramDto(new[] { (item, dto) }, options.Fields, user).GetAwaiter().GetResult();
|
||||||
var task = LivetvManager.AddInfoToProgramDto(list, options.Fields, user);
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item is IItemByName itemByName
|
if (item is IItemByName itemByName
|
||||||
@@ -497,7 +494,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error getting {imageType} image info for {path}", image.Type, image.Path);
|
_logger.LogError(ex, "Error getting {ImageType} image info for {Path}", image.Type, image.Path);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -755,15 +752,6 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit);
|
dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.ContainsField(ItemFields.ScreenshotImageTags))
|
|
||||||
{
|
|
||||||
var screenshotLimit = options.GetImageLimit(ImageType.Screenshot);
|
|
||||||
if (screenshotLimit > 0)
|
|
||||||
{
|
|
||||||
dto.ScreenshotImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Screenshot, screenshotLimit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.ContainsField(ItemFields.Genres))
|
if (options.ContainsField(ItemFields.Genres))
|
||||||
{
|
{
|
||||||
dto.Genres = item.Genres;
|
dto.Genres = item.Genres;
|
||||||
|
|||||||
@@ -25,12 +25,12 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DiscUtils.Udf" Version="0.16.13" />
|
<PackageReference Include="DiscUtils.Udf" Version="0.16.13" />
|
||||||
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
|
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" />
|
||||||
<PackageReference Include="Mono.Nat" Version="3.0.1" />
|
<PackageReference Include="Mono.Nat" Version="3.0.2" />
|
||||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
|
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
|
||||||
<PackageReference Include="sharpcompress" Version="0.30.0" />
|
<PackageReference Include="sharpcompress" Version="0.30.0" />
|
||||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
|
|
||||||
if (!auth.HasToken)
|
if (!auth.HasToken)
|
||||||
{
|
{
|
||||||
throw new AuthenticationException("Request does not contain a token.");
|
return auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!auth.IsAuthenticated)
|
if (!auth.IsAuthenticated)
|
||||||
|
|||||||
@@ -35,7 +35,12 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task WebSocketRequestHandler(HttpContext context)
|
public async Task WebSocketRequestHandler(HttpContext context)
|
||||||
{
|
{
|
||||||
_ = await _authService.Authenticate(context.Request).ConfigureAwait(false);
|
var authorizationInfo = await _authService.Authenticate(context.Request).ConfigureAwait(false);
|
||||||
|
if (!authorizationInfo.IsAuthenticated)
|
||||||
|
{
|
||||||
|
throw new SecurityException("Token is required");
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress);
|
_logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress);
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
if (_fileSystemWatchers.TryAdd(path, newWatcher))
|
if (_fileSystemWatchers.TryAdd(path, newWatcher))
|
||||||
{
|
{
|
||||||
newWatcher.EnableRaisingEvents = true;
|
newWatcher.EnableRaisingEvents = true;
|
||||||
_logger.LogInformation("Watching directory " + path);
|
_logger.LogInformation("Watching directory {Path}", path);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -276,7 +276,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error watching path: {path}", path);
|
_logger.LogError(ex, "Error watching path: {Path}", path);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@@ -23,6 +21,11 @@ namespace Emby.Server.Implementations.IO
|
|||||||
private readonly string _tempPath;
|
private readonly string _tempPath;
|
||||||
private static readonly bool _isEnvironmentCaseInsensitive = OperatingSystem.IsWindows();
|
private static readonly bool _isEnvironmentCaseInsensitive = OperatingSystem.IsWindows();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ManagedFileSystem"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">The <see cref="ILogger"/> instance to use.</param>
|
||||||
|
/// <param name="applicationPaths">The <see cref="IApplicationPaths"/> instance to use.</param>
|
||||||
public ManagedFileSystem(
|
public ManagedFileSystem(
|
||||||
ILogger<ManagedFileSystem> logger,
|
ILogger<ManagedFileSystem> logger,
|
||||||
IApplicationPaths applicationPaths)
|
IApplicationPaths applicationPaths)
|
||||||
@@ -31,6 +34,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
_tempPath = applicationPaths.TempDirectory;
|
_tempPath = applicationPaths.TempDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual void AddShortcutHandler(IShortcutHandler handler)
|
public virtual void AddShortcutHandler(IShortcutHandler handler)
|
||||||
{
|
{
|
||||||
_shortcutHandlers.Add(handler);
|
_shortcutHandlers.Add(handler);
|
||||||
@@ -72,6 +76,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
return handler?.Resolve(filename);
|
return handler?.Resolve(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual string MakeAbsolutePath(string folderPath, string filePath)
|
public virtual string MakeAbsolutePath(string folderPath, string filePath)
|
||||||
{
|
{
|
||||||
// path is actually a stream
|
// path is actually a stream
|
||||||
@@ -358,11 +363,13 @@ namespace Emby.Server.Implementations.IO
|
|||||||
return GetCreationTimeUtc(GetFileSystemInfo(path));
|
return GetCreationTimeUtc(GetFileSystemInfo(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual DateTime GetCreationTimeUtc(FileSystemMetadata info)
|
public virtual DateTime GetCreationTimeUtc(FileSystemMetadata info)
|
||||||
{
|
{
|
||||||
return info.CreationTimeUtc;
|
return info.CreationTimeUtc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual DateTime GetLastWriteTimeUtc(FileSystemMetadata info)
|
public virtual DateTime GetLastWriteTimeUtc(FileSystemMetadata info)
|
||||||
{
|
{
|
||||||
return info.LastWriteTimeUtc;
|
return info.LastWriteTimeUtc;
|
||||||
@@ -397,6 +404,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
return GetLastWriteTimeUtc(GetFileSystemInfo(path));
|
return GetLastWriteTimeUtc(GetFileSystemInfo(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual void SetHidden(string path, bool isHidden)
|
public virtual void SetHidden(string path, bool isHidden)
|
||||||
{
|
{
|
||||||
if (!OperatingSystem.IsWindows())
|
if (!OperatingSystem.IsWindows())
|
||||||
@@ -421,6 +429,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual void SetAttributes(string path, bool isHidden, bool readOnly)
|
public virtual void SetAttributes(string path, bool isHidden, bool readOnly)
|
||||||
{
|
{
|
||||||
if (!OperatingSystem.IsWindows())
|
if (!OperatingSystem.IsWindows())
|
||||||
@@ -444,7 +453,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
|
|
||||||
if (readOnly)
|
if (readOnly)
|
||||||
{
|
{
|
||||||
attributes = attributes | FileAttributes.ReadOnly;
|
attributes |= FileAttributes.ReadOnly;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -453,7 +462,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
|
|
||||||
if (isHidden)
|
if (isHidden)
|
||||||
{
|
{
|
||||||
attributes = attributes | FileAttributes.Hidden;
|
attributes |= FileAttributes.Hidden;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -498,6 +507,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
File.Copy(temp1, file2, true);
|
File.Copy(temp1, file2, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual bool ContainsSubPath(string parentPath, string path)
|
public virtual bool ContainsSubPath(string parentPath, string path)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(parentPath))
|
if (string.IsNullOrEmpty(parentPath))
|
||||||
@@ -515,6 +525,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
_isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
|
_isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual string NormalizePath(string path)
|
public virtual string NormalizePath(string path)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
@@ -530,6 +541,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
return Path.TrimEndingDirectorySeparator(path);
|
return Path.TrimEndingDirectorySeparator(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual bool AreEqual(string path1, string path2)
|
public virtual bool AreEqual(string path1, string path2)
|
||||||
{
|
{
|
||||||
if (path1 == null && path2 == null)
|
if (path1 == null && path2 == null)
|
||||||
@@ -548,6 +560,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
_isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
|
_isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual string GetFileNameWithoutExtension(FileSystemMetadata info)
|
public virtual string GetFileNameWithoutExtension(FileSystemMetadata info)
|
||||||
{
|
{
|
||||||
if (info.IsDirectory)
|
if (info.IsDirectory)
|
||||||
@@ -558,11 +571,11 @@ namespace Emby.Server.Implementations.IO
|
|||||||
return Path.GetFileNameWithoutExtension(info.FullName);
|
return Path.GetFileNameWithoutExtension(info.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual bool IsPathFile(string path)
|
public virtual bool IsPathFile(string path)
|
||||||
{
|
{
|
||||||
// Cannot use Path.IsPathRooted because it returns false under mono when using windows-based paths, e.g. C:\\
|
if (path.Contains("://", StringComparison.OrdinalIgnoreCase)
|
||||||
if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) != -1 &&
|
&& !path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
|
||||||
!path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -570,17 +583,23 @@ namespace Emby.Server.Implementations.IO
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual void DeleteFile(string path)
|
public virtual void DeleteFile(string path)
|
||||||
{
|
{
|
||||||
SetAttributes(path, false, false);
|
SetAttributes(path, false, false);
|
||||||
File.Delete(path);
|
File.Delete(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual List<FileSystemMetadata> GetDrives()
|
public virtual List<FileSystemMetadata> GetDrives()
|
||||||
{
|
{
|
||||||
// check for ready state to avoid waiting for drives to timeout
|
// check for ready state to avoid waiting for drives to timeout
|
||||||
// some drives on linux have no actual size or are used for other purposes
|
// some drives on linux have no actual size or are used for other purposes
|
||||||
return DriveInfo.GetDrives().Where(d => d.IsReady && d.TotalSize != 0 && d.DriveType != DriveType.Ram)
|
return DriveInfo.GetDrives()
|
||||||
|
.Where(
|
||||||
|
d => (d.DriveType == DriveType.Fixed || d.DriveType == DriveType.Network || d.DriveType == DriveType.Removable)
|
||||||
|
&& d.IsReady
|
||||||
|
&& d.TotalSize != 0)
|
||||||
.Select(d => new FileSystemMetadata
|
.Select(d => new FileSystemMetadata
|
||||||
{
|
{
|
||||||
Name = d.Name,
|
Name = d.Name,
|
||||||
@@ -589,16 +608,19 @@ namespace Emby.Server.Implementations.IO
|
|||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
|
public virtual IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
|
||||||
{
|
{
|
||||||
return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", GetEnumerationOptions(recursive)));
|
return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", GetEnumerationOptions(recursive)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false)
|
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false)
|
||||||
{
|
{
|
||||||
return GetFiles(path, null, false, recursive);
|
return GetFiles(path, null, false, recursive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
|
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
|
||||||
{
|
{
|
||||||
var enumerationOptions = GetEnumerationOptions(recursive);
|
var enumerationOptions = GetEnumerationOptions(recursive);
|
||||||
@@ -629,6 +651,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
return ToMetadata(files);
|
return ToMetadata(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
|
public virtual IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
|
||||||
{
|
{
|
||||||
var directoryInfo = new DirectoryInfo(path);
|
var directoryInfo = new DirectoryInfo(path);
|
||||||
@@ -642,16 +665,19 @@ namespace Emby.Server.Implementations.IO
|
|||||||
return infos.Select(GetFileSystemMetadata);
|
return infos.Select(GetFileSystemMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
|
public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
|
||||||
{
|
{
|
||||||
return Directory.EnumerateDirectories(path, "*", GetEnumerationOptions(recursive));
|
return Directory.EnumerateDirectories(path, "*", GetEnumerationOptions(recursive));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual IEnumerable<string> GetFilePaths(string path, bool recursive = false)
|
public virtual IEnumerable<string> GetFilePaths(string path, bool recursive = false)
|
||||||
{
|
{
|
||||||
return GetFilePaths(path, null, false, recursive);
|
return GetFilePaths(path, null, false, recursive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual IEnumerable<string> GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
|
public virtual IEnumerable<string> GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
|
||||||
{
|
{
|
||||||
var enumerationOptions = GetEnumerationOptions(recursive);
|
var enumerationOptions = GetEnumerationOptions(recursive);
|
||||||
@@ -682,6 +708,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
|
public virtual IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
|
||||||
{
|
{
|
||||||
return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive));
|
return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive));
|
||||||
|
|||||||
@@ -333,8 +333,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var task = BaseItem.ChannelManager.DeleteItem(item);
|
BaseItem.ChannelManager.DeleteItem(item).GetAwaiter().GetResult();
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
}
|
||||||
catch (ArgumentException)
|
catch (ArgumentException)
|
||||||
{
|
{
|
||||||
@@ -492,7 +491,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error in {resolver} resolving {path}", resolver.GetType().Name, args.Path);
|
_logger.LogError(ex, "Error in {Resolver} resolving {Path}", resolver.GetType().Name, args.Path);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -799,7 +798,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
var userRootPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
var userRootPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||||
|
|
||||||
_logger.LogDebug("Creating userRootPath at {path}", userRootPath);
|
_logger.LogDebug("Creating userRootPath at {Path}", userRootPath);
|
||||||
Directory.CreateDirectory(userRootPath);
|
Directory.CreateDirectory(userRootPath);
|
||||||
|
|
||||||
var newItemId = GetNewItemId(userRootPath, typeof(UserRootFolder));
|
var newItemId = GetNewItemId(userRootPath, typeof(UserRootFolder));
|
||||||
@@ -810,7 +809,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error creating UserRootFolder {path}", newItemId);
|
_logger.LogError(ex, "Error creating UserRootFolder {Path}", newItemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tmpItem == null)
|
if (tmpItem == null)
|
||||||
@@ -827,7 +826,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
_userRootFolder = tmpItem;
|
_userRootFolder = tmpItem;
|
||||||
_logger.LogDebug("Setting userRootFolder: {folder}", _userRootFolder);
|
_logger.LogDebug("Setting userRootFolder: {Folder}", _userRootFolder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1213,7 +1212,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error resolving shortcut file {file}", i);
|
_logger.LogError(ex, "Error resolving shortcut file {File}", i);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1698,7 +1697,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (video == null)
|
if (video == null)
|
||||||
{
|
{
|
||||||
_logger.LogError("Intro resolver returned null for {path}.", info.Path);
|
_logger.LogError("Intro resolver returned null for {Path}.", info.Path);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1717,7 +1716,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error resolving path {path}.", info.Path);
|
_logger.LogError(ex, "Error resolving path {Path}.", info.Path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly ILocalizationManager _localizationManager;
|
private readonly ILocalizationManager _localizationManager;
|
||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
|
private readonly IDirectoryService _directoryService;
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
|
||||||
@@ -61,7 +62,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
ILogger<MediaSourceManager> logger,
|
ILogger<MediaSourceManager> logger,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IUserDataManager userDataManager,
|
IUserDataManager userDataManager,
|
||||||
IMediaEncoder mediaEncoder)
|
IMediaEncoder mediaEncoder,
|
||||||
|
IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
_itemRepo = itemRepo;
|
_itemRepo = itemRepo;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
@@ -72,6 +74,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_localizationManager = localizationManager;
|
_localizationManager = localizationManager;
|
||||||
_appPaths = applicationPaths;
|
_appPaths = applicationPaths;
|
||||||
|
_directoryService = directoryService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddParts(IEnumerable<IMediaSourceProvider> providers)
|
public void AddParts(IEnumerable<IMediaSourceProvider> providers)
|
||||||
@@ -106,16 +109,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MediaStream> GetMediaStreams(string mediaSourceId)
|
|
||||||
{
|
|
||||||
var list = GetMediaStreams(new MediaStreamQuery
|
|
||||||
{
|
|
||||||
ItemId = new Guid(mediaSourceId)
|
|
||||||
});
|
|
||||||
|
|
||||||
return GetMediaStreamsForItem(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<MediaStream> GetMediaStreams(Guid itemId)
|
public List<MediaStream> GetMediaStreams(Guid itemId)
|
||||||
{
|
{
|
||||||
var list = GetMediaStreams(new MediaStreamQuery
|
var list = GetMediaStreams(new MediaStreamQuery
|
||||||
@@ -161,7 +154,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video))
|
if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video))
|
||||||
{
|
{
|
||||||
await item.RefreshMetadata(
|
await item.RefreshMetadata(
|
||||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
new MetadataRefreshOptions(_directoryService)
|
||||||
{
|
{
|
||||||
EnableRemoteContentProbe = true,
|
EnableRemoteContentProbe = true,
|
||||||
MetadataRefreshMode = MetadataRefreshMode.FullRefresh
|
MetadataRefreshMode = MetadataRefreshMode.FullRefresh
|
||||||
@@ -212,6 +205,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return SortMediaSources(list);
|
return SortMediaSources(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />>
|
||||||
public MediaProtocol GetPathProtocol(string path)
|
public MediaProtocol GetPathProtocol(string path)
|
||||||
{
|
{
|
||||||
if (path.StartsWith("Rtsp", StringComparison.OrdinalIgnoreCase))
|
if (path.StartsWith("Rtsp", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -258,7 +252,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
if (path != null)
|
if (path != null)
|
||||||
{
|
{
|
||||||
if (path.IndexOf(".m3u", StringComparison.OrdinalIgnoreCase) != -1)
|
if (path.Contains(".m3u", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -297,7 +291,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error getting media sources");
|
_logger.LogError(ex, "Error getting media sources");
|
||||||
return new List<MediaSourceInfo>();
|
return Enumerable.Empty<MediaSourceInfo>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,14 +488,11 @@ namespace Emby.Server.Implementations.Library
|
|||||||
_liveStreamSemaphore.Release();
|
_liveStreamSemaphore.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Don't hardcode this
|
|
||||||
const bool isAudio = false;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (mediaSource.MediaStreams.Any(i => i.Index != -1) || !mediaSource.SupportsProbing)
|
if (mediaSource.MediaStreams.Any(i => i.Index != -1) || !mediaSource.SupportsProbing)
|
||||||
{
|
{
|
||||||
AddMediaInfo(mediaSource, isAudio);
|
AddMediaInfo(mediaSource);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -509,14 +500,14 @@ namespace Emby.Server.Implementations.Library
|
|||||||
string cacheKey = request.OpenToken;
|
string cacheKey = request.OpenToken;
|
||||||
|
|
||||||
await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)
|
await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)
|
||||||
.AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken)
|
.AddMediaInfoWithProbe(mediaSource, false, cacheKey, true, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error probing live tv stream");
|
_logger.LogError(ex, "Error probing live tv stream");
|
||||||
AddMediaInfo(mediaSource, isAudio);
|
AddMediaInfo(mediaSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: @bond Fix
|
// TODO: @bond Fix
|
||||||
@@ -536,7 +527,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider);
|
return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio)
|
private static void AddMediaInfo(MediaSourceInfo mediaSource)
|
||||||
{
|
{
|
||||||
mediaSource.DefaultSubtitleStreamIndex = null;
|
mediaSource.DefaultSubtitleStreamIndex = null;
|
||||||
|
|
||||||
@@ -855,9 +846,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return (provider, keyId);
|
return (provider, keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
Dispose(true);
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
{
|
{
|
||||||
if (parser.IsMultiPart(path))
|
if (parser.IsMultiPart(path))
|
||||||
{
|
{
|
||||||
logger.LogDebug("Found multi-disc folder: " + path);
|
logger.LogDebug("Found multi-disc folder: {Path}", path);
|
||||||
Interlocked.Increment(ref discSubfolderCount);
|
Interlocked.Increment(ref discSubfolderCount);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var seriesInfo = Naming.TV.SeriesResolver.Resolve(_libraryManager.GetNamingOptions(), args.Path);
|
||||||
|
|
||||||
var collectionType = args.GetCollectionType();
|
var collectionType = args.GetCollectionType();
|
||||||
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@@ -63,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||||||
return new Series
|
return new Series
|
||||||
{
|
{
|
||||||
Path = args.Path,
|
Path = args.Path,
|
||||||
Name = Path.GetFileName(args.Path)
|
Name = seriesInfo.Name
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,7 +82,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||||||
return new Series
|
return new Series
|
||||||
{
|
{
|
||||||
Path = args.Path,
|
Path = args.Path,
|
||||||
Name = Path.GetFileName(args.Path)
|
Name = seriesInfo.Name
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +96,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||||||
return new Series
|
return new Series
|
||||||
{
|
{
|
||||||
Path = args.Path,
|
Path = args.Path,
|
||||||
Name = Path.GetFileName(args.Path)
|
Name = seriesInfo.Name
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error validating IBN entry {person}", person);
|
_logger.LogError(ex, "Error validating IBN entry {Person}", person);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update progress
|
// Update progress
|
||||||
|
|||||||
@@ -957,7 +957,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
public async Task<ILiveStream> GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
|
public async Task<ILiveStream> GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Streaming Channel " + channelId);
|
_logger.LogInformation("Streaming Channel {Id}", channelId);
|
||||||
|
|
||||||
var result = string.IsNullOrEmpty(streamId) ?
|
var result = string.IsNullOrEmpty(streamId) ?
|
||||||
null :
|
null :
|
||||||
@@ -1027,7 +1027,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
{
|
{
|
||||||
var stream = new MediaSourceInfo
|
var stream = new MediaSourceInfo
|
||||||
{
|
{
|
||||||
EncoderPath = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
|
EncoderPath = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
|
||||||
EncoderProtocol = MediaProtocol.Http,
|
EncoderProtocol = MediaProtocol.Http,
|
||||||
Path = info.Path,
|
Path = info.Path,
|
||||||
Protocol = MediaProtocol.File,
|
Protocol = MediaProtocol.File,
|
||||||
@@ -1308,16 +1308,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
await recorder.Record(directStreamProvider, mediaStreamInfo, recordPath, duration, onStarted, activeRecordingInfo.CancellationTokenSource.Token).ConfigureAwait(false);
|
await recorder.Record(directStreamProvider, mediaStreamInfo, recordPath, duration, onStarted, activeRecordingInfo.CancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
recordingStatus = RecordingStatus.Completed;
|
recordingStatus = RecordingStatus.Completed;
|
||||||
_logger.LogInformation("Recording completed: {recordPath}", recordPath);
|
_logger.LogInformation("Recording completed: {RecordPath}", recordPath);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Recording stopped: {recordPath}", recordPath);
|
_logger.LogInformation("Recording stopped: {RecordPath}", recordPath);
|
||||||
recordingStatus = RecordingStatus.Completed;
|
recordingStatus = RecordingStatus.Completed;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error recording to {recordPath}", recordPath);
|
_logger.LogError(ex, "Error recording to {RecordPath}", recordPath);
|
||||||
recordingStatus = RecordingStatus.Error;
|
recordingStatus = RecordingStatus.Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1404,7 +1404,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error deleting 0-byte failed recording file {path}", path);
|
_logger.LogError(ex, "Error deleting 0-byte failed recording file {Path}", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,8 +87,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
ErrorDialog = false
|
ErrorDialog = false
|
||||||
};
|
};
|
||||||
|
|
||||||
var commandLineLogMessage = processStartInfo.FileName + " " + processStartInfo.Arguments;
|
_logger.LogInformation("{Filename} {Arguments}", processStartInfo.FileName, processStartInfo.Arguments);
|
||||||
_logger.LogInformation(commandLineLogMessage);
|
|
||||||
|
|
||||||
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
|
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
|
||||||
@@ -97,7 +96,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
_logFileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
|
_logFileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
|
||||||
|
|
||||||
await JsonSerializer.SerializeAsync(_logFileStream, mediaSource, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
await JsonSerializer.SerializeAsync(_logFileStream, mediaSource, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||||
await _logFileStream.WriteAsync(Encoding.UTF8.GetBytes(Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine), cancellationToken).ConfigureAwait(false);
|
await _logFileStream.WriteAsync(Encoding.UTF8.GetBytes(Environment.NewLine + Environment.NewLine + processStartInfo.FileName + " " + processStartInfo.Arguments + Environment.NewLine + Environment.NewLine), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
_process = new Process
|
_process = new Process
|
||||||
{
|
{
|
||||||
@@ -225,13 +224,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Stopping ffmpeg recording process for {path}", _targetPath);
|
_logger.LogInformation("Stopping ffmpeg recording process for {Path}", _targetPath);
|
||||||
|
|
||||||
_process.StandardInput.WriteLine("q");
|
_process.StandardInput.WriteLine("q");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error stopping recording transcoding job for {path}", _targetPath);
|
_logger.LogError(ex, "Error stopping recording transcoding job for {Path}", _targetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_hasExited)
|
if (_hasExited)
|
||||||
@@ -241,7 +240,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Calling recording process.WaitForExit for {path}", _targetPath);
|
_logger.LogInformation("Calling recording process.WaitForExit for {Path}", _targetPath);
|
||||||
|
|
||||||
if (_process.WaitForExit(10000))
|
if (_process.WaitForExit(10000))
|
||||||
{
|
{
|
||||||
@@ -250,7 +249,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error waiting for recording process to exit for {path}", _targetPath);
|
_logger.LogError(ex, "Error waiting for recording process to exit for {Path}", _targetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_hasExited)
|
if (_hasExited)
|
||||||
@@ -260,13 +259,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Killing ffmpeg recording process for {path}", _targetPath);
|
_logger.LogInformation("Killing ffmpeg recording process for {Path}", _targetPath);
|
||||||
|
|
||||||
_process.Kill();
|
_process.Kill();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error killing recording transcoding job for {path}", _targetPath);
|
_logger.LogError(ex, "Error killing recording transcoding job for {Path}", _targetPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -393,7 +393,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error getting image info for {name}", info.Name);
|
_logger.LogError(ex, "Error getting image info for {Name}", info.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1054,7 +1054,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
_logger.LogDebug("Refreshing guide from {name}", service.Name);
|
_logger.LogDebug("Refreshing guide from {Name}", service.Name);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1135,7 +1135,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error getting channel information for {name}", channelInfo.Item2.Name);
|
_logger.LogError(ex, "Error getting channel information for {Name}", channelInfo.Item2.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
numComplete++;
|
numComplete++;
|
||||||
@@ -1248,7 +1248,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error getting programs for channel {name}", currentChannel.Name);
|
_logger.LogError(ex, "Error getting programs for channel {Name}", currentChannel.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
numComplete++;
|
numComplete++;
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
// Dummy this up so that direct play checks can still run
|
// Dummy this up so that direct play checks can still run
|
||||||
if (string.IsNullOrEmpty(source.Path) && source.Protocol == MediaProtocol.Http)
|
if (string.IsNullOrEmpty(source.Path) && source.Protocol == MediaProtocol.Http)
|
||||||
{
|
{
|
||||||
source.Path = _appHost.GetSmartApiUrl(string.Empty);
|
source.Path = _appHost.GetApiUrlForLocalAccess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath));
|
||||||
|
|
||||||
Logger.LogInformation("Opening HDHR UDP Live stream from {host}", uri.Host);
|
Logger.LogInformation("Opening HDHR UDP Live stream from {Host}", uri.Host);
|
||||||
|
|
||||||
var remoteAddress = IPAddress.Parse(uri.Host);
|
var remoteAddress = IPAddress.Parse(uri.Host);
|
||||||
IPAddress localAddress = null;
|
IPAddress localAddress = null;
|
||||||
@@ -147,7 +147,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||||||
// OpenedMediaSource.Path = tempFile;
|
// OpenedMediaSource.Path = tempFile;
|
||||||
// OpenedMediaSource.ReadAtNativeFramerate = true;
|
// OpenedMediaSource.ReadAtNativeFramerate = true;
|
||||||
|
|
||||||
MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
|
MediaSource.Path = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
|
||||||
MediaSource.Protocol = MediaProtocol.Http;
|
MediaSource.Protocol = MediaProtocol.Http;
|
||||||
// OpenedMediaSource.SupportsDirectPlay = false;
|
// OpenedMediaSource.SupportsDirectPlay = false;
|
||||||
// OpenedMediaSource.SupportsDirectStream = true;
|
// OpenedMediaSource.SupportsDirectStream = true;
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||||||
// OpenedMediaSource.Path = tempFile;
|
// OpenedMediaSource.Path = tempFile;
|
||||||
// OpenedMediaSource.ReadAtNativeFramerate = true;
|
// OpenedMediaSource.ReadAtNativeFramerate = true;
|
||||||
|
|
||||||
MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
|
MediaSource.Path = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
|
||||||
MediaSource.Protocol = MediaProtocol.Http;
|
MediaSource.Protocol = MediaProtocol.Http;
|
||||||
|
|
||||||
// OpenedMediaSource.Path = TempFilePath;
|
// OpenedMediaSource.Path = TempFilePath;
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"NotificationOptionInstallationFailed": "Instalada fiasko",
|
"NotificationOptionInstallationFailed": "Instalada malsukceso",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Ludado de sono haltis",
|
"NotificationOptionAudioPlaybackStopped": "Ludado de sono haltis",
|
||||||
"NotificationOptionAudioPlayback": "Ludado de sono lanĉis",
|
"NotificationOptionAudioPlayback": "Ludado de sono lanĉis",
|
||||||
"NameSeasonUnknown": "Sezono Nekonata",
|
"NameSeasonUnknown": "Sezono Nekonata",
|
||||||
@@ -48,17 +48,17 @@
|
|||||||
"Shows": "Serioj",
|
"Shows": "Serioj",
|
||||||
"HeaderFavoriteShows": "Favorataj Serioj",
|
"HeaderFavoriteShows": "Favorataj Serioj",
|
||||||
"TvShows": "TV-serioj",
|
"TvShows": "TV-serioj",
|
||||||
"Favorites": "Favoratoj",
|
"Favorites": "Favorataj",
|
||||||
"TaskCleanLogs": "Purigi Ĵurnalan Katalogon",
|
"TaskCleanLogs": "Purigi Ĵurnalan Katalogon",
|
||||||
"TaskRefreshLibrary": "Skanu Plurmeditekon",
|
"TaskRefreshLibrary": "Skani Plurmeditekon",
|
||||||
"ValueSpecialEpisodeName": "Speciala - {0}",
|
"ValueSpecialEpisodeName": "Speciala - {0}",
|
||||||
"TaskOptimizeDatabase": "Optimigi datumbazon",
|
"TaskOptimizeDatabase": "Optimumigi datenbazon",
|
||||||
"TaskRefreshChannels": "Refreŝigi Kanalojn",
|
"TaskRefreshChannels": "Refreŝigi Kanalojn",
|
||||||
"TaskUpdatePlugins": "Ĝisdatigi Kromprogramojn",
|
"TaskUpdatePlugins": "Ĝisdatigi Kromprogramojn",
|
||||||
"TaskRefreshPeople": "Refreŝigi Homojn",
|
"TaskRefreshPeople": "Refreŝigi Homojn",
|
||||||
"TasksChannelsCategory": "Interretaj Kanaloj",
|
"TasksChannelsCategory": "Interretaj Kanaloj",
|
||||||
"ProviderValue": "Provizanto: {0}",
|
"ProviderValue": "Provizanto: {0}",
|
||||||
"NotificationOptionPluginError": "Kromprograma malsukceso",
|
"NotificationOptionPluginError": "Kromprogramo malsukcesis",
|
||||||
"MixedContent": "Miksita enhavo",
|
"MixedContent": "Miksita enhavo",
|
||||||
"TasksApplicationCategory": "Aplikaĵo",
|
"TasksApplicationCategory": "Aplikaĵo",
|
||||||
"TasksMaintenanceCategory": "Prizorgado",
|
"TasksMaintenanceCategory": "Prizorgado",
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
"ServerNameNeedsToBeRestarted": "{0} devas esti relanĉita",
|
"ServerNameNeedsToBeRestarted": "{0} devas esti relanĉita",
|
||||||
"NotificationOptionVideoPlayback": "La videoludado lanĉis",
|
"NotificationOptionVideoPlayback": "La videoludado lanĉis",
|
||||||
"NotificationOptionServerRestartRequired": "Servila relanĉigo bezonata",
|
"NotificationOptionServerRestartRequired": "Servila relanĉigo bezonata",
|
||||||
"TaskOptimizeDatabaseDescription": "Kompaktigas datenbazon kaj trunkas liberan lokon. Lanĉi ĉi tiun taskon post la teka skanado aŭ fari aliajn ŝanĝojn, kiuj implicas datenbazajn modifojn, povus plibonigi rendimenton.",
|
"TaskOptimizeDatabaseDescription": "Kompaktigas datenbazon kaj trunkas liberan lokon. Lanĉi ĉi tiun taskon post la plurmediteka skanado aŭ fari aliajn ŝanĝojn, kiuj implicas datenbazajn modifojn, povus plibonigi rendimenton.",
|
||||||
"TaskUpdatePluginsDescription": "Elŝutas kaj instalas ĝisdatigojn por kromprogramojn, kiuj estas agorditaj por ĝisdatigi aŭtomate.",
|
"TaskUpdatePluginsDescription": "Elŝutas kaj instalas ĝisdatigojn por kromprogramojn, kiuj estas agorditaj por ĝisdatigi aŭtomate.",
|
||||||
"TaskDownloadMissingSubtitlesDescription": "Serĉas en interreto mankantajn subtekstojn surbaze de metadatena agordaro.",
|
"TaskDownloadMissingSubtitlesDescription": "Serĉas en interreto mankantajn subtekstojn surbaze de metadatena agordaro.",
|
||||||
"TaskRefreshPeopleDescription": "Ĝisdatigas metadatenojn por aktoroj kaj reĵisoroj en via plurmediteko.",
|
"TaskRefreshPeopleDescription": "Ĝisdatigas metadatenojn por aktoroj kaj reĵisoroj en via plurmediteko.",
|
||||||
@@ -102,9 +102,9 @@
|
|||||||
"MessageApplicationUpdatedTo": "Jellyfin Server estis ĝisdatigita al {0}",
|
"MessageApplicationUpdatedTo": "Jellyfin Server estis ĝisdatigita al {0}",
|
||||||
"MessageApplicationUpdated": "Jellyfin Server estis ĝisdatigita",
|
"MessageApplicationUpdated": "Jellyfin Server estis ĝisdatigita",
|
||||||
"TaskRefreshChannelsDescription": "Refreŝigas informon pri interretaj kanaloj.",
|
"TaskRefreshChannelsDescription": "Refreŝigas informon pri interretaj kanaloj.",
|
||||||
"TaskDownloadMissingSubtitles": "Elŝutu mankantajn subtekstojn",
|
"TaskDownloadMissingSubtitles": "Elŝuti mankantajn subtekstojn",
|
||||||
"TaskCleanTranscode": "Malplenigi Transkodadan Katalogon",
|
"TaskCleanTranscode": "Malplenigi Transkodadan Katalogon",
|
||||||
"TaskRefreshChapterImages": "Eltiru Ĉapitro-Bildojn",
|
"TaskRefreshChapterImages": "Eltiri Ĉapitraj Bildojn",
|
||||||
"TaskCleanCache": "Malplenigi Staplan Katalogon",
|
"TaskCleanCache": "Malplenigi Staplan Katalogon",
|
||||||
"TaskCleanActivityLog": "Malplenigi Aktivecan Ĵurnalon",
|
"TaskCleanActivityLog": "Malplenigi Aktivecan Ĵurnalon",
|
||||||
"PluginUpdatedWithName": "{0} estis ĝisdatigita",
|
"PluginUpdatedWithName": "{0} estis ĝisdatigita",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"TaskCleanLogsDescription": "Kustutab logifailid, mis on vanemad kui {0} päeva.",
|
"TaskCleanLogsDescription": "Kustutab logifailid, mis on vanemad kui {0} päeva.",
|
||||||
"TaskCleanLogs": "Puhasta logikataloog",
|
"TaskCleanLogs": "Puhasta logikataloog",
|
||||||
"TaskRefreshLibraryDescription": "Otsib meedikogust uusi faile ja värskendab metaandmeid.",
|
"TaskRefreshLibraryDescription": "Otsib meedikogust uusi faile ja värskendab metaandmeid.",
|
||||||
"Collections": "Kollektsioonid",
|
"Collections": "Kogumikud",
|
||||||
"TaskRefreshLibrary": "Skaneeri meediakogu",
|
"TaskRefreshLibrary": "Skaneeri meediakogu",
|
||||||
"TaskRefreshChapterImagesDescription": "Loob peatükkidega videote jaoks pisipildid.",
|
"TaskRefreshChapterImagesDescription": "Loob peatükkidega videote jaoks pisipildid.",
|
||||||
"TaskRefreshChapterImages": "Eralda peatükipildid",
|
"TaskRefreshChapterImages": "Eralda peatükipildid",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
"MixedContent": "Conteúdo Misto",
|
"MixedContent": "Conteúdo Misto",
|
||||||
"Movies": "Filmes",
|
"Movies": "Filmes",
|
||||||
"Music": "Música",
|
"Music": "Música",
|
||||||
"MusicVideos": "Videoclips",
|
"MusicVideos": "Videoclipes",
|
||||||
"NameInstallFailed": "{0} falha na instalação",
|
"NameInstallFailed": "{0} falha na instalação",
|
||||||
"NameSeasonNumber": "Temporada {0}",
|
"NameSeasonNumber": "Temporada {0}",
|
||||||
"NameSeasonUnknown": "Temporada Desconhecida",
|
"NameSeasonUnknown": "Temporada Desconhecida",
|
||||||
@@ -118,5 +118,7 @@
|
|||||||
"TaskCleanActivityLog": "Limpar registo de atividade",
|
"TaskCleanActivityLog": "Limpar registo de atividade",
|
||||||
"Undefined": "Indefinido",
|
"Undefined": "Indefinido",
|
||||||
"Forced": "Forçado",
|
"Forced": "Forçado",
|
||||||
"Default": "Padrão"
|
"Default": "Padrão",
|
||||||
|
"TaskOptimizeDatabaseDescription": "Base de dados compacta e corta espaço livre. A execução desta tarefa depois de digitalizar a biblioteca ou de fazer outras alterações que impliquem modificações na base de dados pode melhorar o desempenho.",
|
||||||
|
"TaskOptimizeDatabase": "Otimizar base de dados"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
"ItemRemovedWithName": "{0} - изъято из медиатеки",
|
"ItemRemovedWithName": "{0} - изъято из медиатеки",
|
||||||
"LabelIpAddressValue": "IP-адрес: {0}",
|
"LabelIpAddressValue": "IP-адрес: {0}",
|
||||||
"LabelRunningTimeValue": "Длительность: {0}",
|
"LabelRunningTimeValue": "Длительность: {0}",
|
||||||
"Latest": "Последнее",
|
"Latest": "Крайнее",
|
||||||
"MessageApplicationUpdated": "Jellyfin Server был обновлён",
|
"MessageApplicationUpdated": "Jellyfin Server был обновлён",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}",
|
"MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "Конфигурация сервера (раздел {0}) была обновлена",
|
"MessageNamedServerConfigurationUpdatedWithValue": "Конфигурация сервера (раздел {0}) была обновлена",
|
||||||
@@ -119,6 +119,6 @@
|
|||||||
"Undefined": "Не определено",
|
"Undefined": "Не определено",
|
||||||
"Forced": "Форсир-ые",
|
"Forced": "Форсир-ые",
|
||||||
"Default": "По умолчанию",
|
"Default": "По умолчанию",
|
||||||
"TaskOptimizeDatabaseDescription": "Сжимает базу данных и обрезает свободное место. Выполнение этой задачи после сканирования библиотеки или внесения других изменений, предполагающих модификации базы данных, может повысить производительность.",
|
"TaskOptimizeDatabaseDescription": "Сжимает базу данных и вырезает свободные места. Выполнение этой задачи после сканирования библиотеки или внесения других изменений, предполагающих модификации базы данных, может повысить производительность.",
|
||||||
"TaskOptimizeDatabase": "Оптимизировать базу данных"
|
"TaskOptimizeDatabase": "Оптимизировать базу данных"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
"MixedContent": "Zmiešaný obsah",
|
"MixedContent": "Zmiešaný obsah",
|
||||||
"Movies": "Filmy",
|
"Movies": "Filmy",
|
||||||
"Music": "Hudba",
|
"Music": "Hudba",
|
||||||
"MusicVideos": "Hudobné videá",
|
"MusicVideos": "Hudobné videoklipy",
|
||||||
"NameInstallFailed": "Inštalácia {0} zlyhala",
|
"NameInstallFailed": "Inštalácia {0} zlyhala",
|
||||||
"NameSeasonNumber": "Séria {0}",
|
"NameSeasonNumber": "Séria {0}",
|
||||||
"NameSeasonUnknown": "Neznáma séria",
|
"NameSeasonUnknown": "Neznáma séria",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
"CameraImageUploadedFrom": "{0} 'den yeni bir kamera resmi yüklendi",
|
"CameraImageUploadedFrom": "{0} 'den yeni bir kamera resmi yüklendi",
|
||||||
"Channels": "Kanallar",
|
"Channels": "Kanallar",
|
||||||
"ChapterNameValue": "Bölüm {0}",
|
"ChapterNameValue": "Bölüm {0}",
|
||||||
"Collections": "Koleksiyon",
|
"Collections": "Koleksiyonlar",
|
||||||
"DeviceOfflineWithName": "{0} bağlantısı kesildi",
|
"DeviceOfflineWithName": "{0} bağlantısı kesildi",
|
||||||
"DeviceOnlineWithName": "{0} bağlı",
|
"DeviceOnlineWithName": "{0} bağlı",
|
||||||
"FailedLoginAttemptWithUserName": "{0} adresinden giriş başarısız oldu",
|
"FailedLoginAttemptWithUserName": "{0} adresinden giriş başarısız oldu",
|
||||||
|
|||||||
@@ -62,11 +62,11 @@
|
|||||||
"PluginUninstalledWithName": "{0} đã được gỡ bỏ",
|
"PluginUninstalledWithName": "{0} đã được gỡ bỏ",
|
||||||
"PluginInstalledWithName": "{0} đã được cài đặt",
|
"PluginInstalledWithName": "{0} đã được cài đặt",
|
||||||
"Plugin": "Plugin",
|
"Plugin": "Plugin",
|
||||||
"NotificationOptionVideoPlaybackStopped": "Phát lại video đã dừng",
|
"NotificationOptionVideoPlaybackStopped": "Đã dừng phát lại video",
|
||||||
"NotificationOptionVideoPlayback": "Đã bắt đầu phát lại video",
|
"NotificationOptionVideoPlayback": "Đã bắt đầu phát lại video",
|
||||||
"NotificationOptionUserLockedOut": "Người dùng bị khóa",
|
"NotificationOptionUserLockedOut": "Người dùng bị khóa",
|
||||||
"NotificationOptionTaskFailed": "Lỗi tác vụ đã lên lịch",
|
"NotificationOptionTaskFailed": "Lỗi tác vụ đã lên lịch",
|
||||||
"NotificationOptionServerRestartRequired": "Yêu cầu khởi động lại Server",
|
"NotificationOptionServerRestartRequired": "Yêu cầu khởi động lại máy chủ",
|
||||||
"NotificationOptionPluginUpdateInstalled": "Cập nhật Plugin đã được cài đặt",
|
"NotificationOptionPluginUpdateInstalled": "Cập nhật Plugin đã được cài đặt",
|
||||||
"NotificationOptionPluginUninstalled": "Đã gỡ bỏ Plugin",
|
"NotificationOptionPluginUninstalled": "Đã gỡ bỏ Plugin",
|
||||||
"NotificationOptionPluginInstalled": "Đã cài đặt Plugin",
|
"NotificationOptionPluginInstalled": "Đã cài đặt Plugin",
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
"NotificationOptionInstallationFailed": "Cài đặt thất bại",
|
"NotificationOptionInstallationFailed": "Cài đặt thất bại",
|
||||||
"NotificationOptionCameraImageUploaded": "Đã tải lên hình ảnh máy ảnh",
|
"NotificationOptionCameraImageUploaded": "Đã tải lên hình ảnh máy ảnh",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Phát lại âm thanh đã dừng",
|
"NotificationOptionAudioPlaybackStopped": "Phát lại âm thanh đã dừng",
|
||||||
"NotificationOptionAudioPlayback": "Phát lại âm thanh đã bắt đầu",
|
"NotificationOptionAudioPlayback": "Đã bắt đầu phát lại âm thanh",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Bản cập nhật ứng dụng đã được cài đặt",
|
"NotificationOptionApplicationUpdateInstalled": "Bản cập nhật ứng dụng đã được cài đặt",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "Bản cập nhật ứng dụng hiện sẵn có",
|
"NotificationOptionApplicationUpdateAvailable": "Bản cập nhật ứng dụng hiện sẵn có",
|
||||||
"NewVersionIsAvailable": "Một phiên bản mới của Jellyfin Server sẵn có để tải.",
|
"NewVersionIsAvailable": "Một phiên bản mới của Jellyfin Server sẵn có để tải.",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -372,9 +372,11 @@ namespace Emby.Server.Implementations.Localization
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<LocalizationOption> GetLocalizationOptions()
|
public IEnumerable<LocalizationOption> GetLocalizationOptions()
|
||||||
{
|
{
|
||||||
|
yield return new LocalizationOption("Afrikaans", "af");
|
||||||
yield return new LocalizationOption("Arabic", "ar");
|
yield return new LocalizationOption("Arabic", "ar");
|
||||||
yield return new LocalizationOption("Bulgarian (Bulgaria)", "bg-BG");
|
yield return new LocalizationOption("Bulgarian (Bulgaria)", "bg-BG");
|
||||||
yield return new LocalizationOption("Catalan", "ca");
|
yield return new LocalizationOption("Catalan", "ca");
|
||||||
|
yield return new LocalizationOption("Chinese (Hong Kong)", "zh-HK");
|
||||||
yield return new LocalizationOption("Chinese Simplified", "zh-CN");
|
yield return new LocalizationOption("Chinese Simplified", "zh-CN");
|
||||||
yield return new LocalizationOption("Chinese Traditional", "zh-TW");
|
yield return new LocalizationOption("Chinese Traditional", "zh-TW");
|
||||||
yield return new LocalizationOption("Croatian", "hr");
|
yield return new LocalizationOption("Croatian", "hr");
|
||||||
@@ -383,32 +385,48 @@ namespace Emby.Server.Implementations.Localization
|
|||||||
yield return new LocalizationOption("Dutch", "nl");
|
yield return new LocalizationOption("Dutch", "nl");
|
||||||
yield return new LocalizationOption("English (United Kingdom)", "en-GB");
|
yield return new LocalizationOption("English (United Kingdom)", "en-GB");
|
||||||
yield return new LocalizationOption("English (United States)", "en-US");
|
yield return new LocalizationOption("English (United States)", "en-US");
|
||||||
|
yield return new LocalizationOption("Esperanto", "eo");
|
||||||
|
yield return new LocalizationOption("Estonian", "et");
|
||||||
|
yield return new LocalizationOption("Finnish", "fi");
|
||||||
yield return new LocalizationOption("French", "fr");
|
yield return new LocalizationOption("French", "fr");
|
||||||
yield return new LocalizationOption("French (Canada)", "fr-CA");
|
yield return new LocalizationOption("French (Canada)", "fr-CA");
|
||||||
yield return new LocalizationOption("German", "de");
|
yield return new LocalizationOption("German", "de");
|
||||||
yield return new LocalizationOption("Greek", "el");
|
yield return new LocalizationOption("Greek", "el");
|
||||||
yield return new LocalizationOption("Hebrew", "he");
|
yield return new LocalizationOption("Hebrew", "he");
|
||||||
yield return new LocalizationOption("Hungarian", "hu");
|
yield return new LocalizationOption("Hungarian", "hu");
|
||||||
|
yield return new LocalizationOption("Icelandic", "is");
|
||||||
|
yield return new LocalizationOption("Indonesian", "id");
|
||||||
yield return new LocalizationOption("Italian", "it");
|
yield return new LocalizationOption("Italian", "it");
|
||||||
|
yield return new LocalizationOption("Japanese", "ja");
|
||||||
yield return new LocalizationOption("Kazakh", "kk");
|
yield return new LocalizationOption("Kazakh", "kk");
|
||||||
yield return new LocalizationOption("Korean", "ko");
|
yield return new LocalizationOption("Korean", "ko");
|
||||||
|
yield return new LocalizationOption("Latvian", "lv");
|
||||||
yield return new LocalizationOption("Lithuanian", "lt-LT");
|
yield return new LocalizationOption("Lithuanian", "lt-LT");
|
||||||
yield return new LocalizationOption("Malay", "ms");
|
yield return new LocalizationOption("Malay", "ms");
|
||||||
|
yield return new LocalizationOption("Malayalam", "ml");
|
||||||
yield return new LocalizationOption("Norwegian Bokmål", "nb");
|
yield return new LocalizationOption("Norwegian Bokmål", "nb");
|
||||||
|
yield return new LocalizationOption("Norwegian Nynorsk", "nn");
|
||||||
yield return new LocalizationOption("Persian", "fa");
|
yield return new LocalizationOption("Persian", "fa");
|
||||||
yield return new LocalizationOption("Polish", "pl");
|
yield return new LocalizationOption("Polish", "pl");
|
||||||
|
yield return new LocalizationOption("Portuguese", "pt");
|
||||||
yield return new LocalizationOption("Portuguese (Brazil)", "pt-BR");
|
yield return new LocalizationOption("Portuguese (Brazil)", "pt-BR");
|
||||||
yield return new LocalizationOption("Portuguese (Portugal)", "pt-PT");
|
yield return new LocalizationOption("Portuguese (Portugal)", "pt-PT");
|
||||||
|
yield return new LocalizationOption("Romanian", "ro");
|
||||||
yield return new LocalizationOption("Russian", "ru");
|
yield return new LocalizationOption("Russian", "ru");
|
||||||
|
yield return new LocalizationOption("Serbian", "sr");
|
||||||
yield return new LocalizationOption("Slovak", "sk");
|
yield return new LocalizationOption("Slovak", "sk");
|
||||||
yield return new LocalizationOption("Slovenian (Slovenia)", "sl-SI");
|
yield return new LocalizationOption("Slovenian (Slovenia)", "sl-SI");
|
||||||
yield return new LocalizationOption("Spanish", "es");
|
yield return new LocalizationOption("Spanish", "es");
|
||||||
yield return new LocalizationOption("Spanish (Argentina)", "es-AR");
|
yield return new LocalizationOption("Spanish (Argentina)", "es-AR");
|
||||||
|
yield return new LocalizationOption("Spanish (Latin America)", "es-419");
|
||||||
yield return new LocalizationOption("Spanish (Mexico)", "es-MX");
|
yield return new LocalizationOption("Spanish (Mexico)", "es-MX");
|
||||||
yield return new LocalizationOption("Swedish", "sv");
|
yield return new LocalizationOption("Swedish", "sv");
|
||||||
yield return new LocalizationOption("Swiss German", "gsw");
|
yield return new LocalizationOption("Swiss German", "gsw");
|
||||||
|
yield return new LocalizationOption("Tamil", "ta");
|
||||||
|
yield return new LocalizationOption("Telugu", "te");
|
||||||
yield return new LocalizationOption("Turkish", "tr");
|
yield return new LocalizationOption("Turkish", "tr");
|
||||||
yield return new LocalizationOption("Tiếng Việt", "vi");
|
yield return new LocalizationOption("Tiếng Việt", "vi");
|
||||||
|
yield return new LocalizationOption("Ukrainian", "uk");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -630,7 +630,7 @@
|
|||||||
"TwoLetterISORegionName": "MD"
|
"TwoLetterISORegionName": "MD"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"DisplayName": "Réunion",
|
"DisplayName": "Réunion",
|
||||||
"Name": "RE",
|
"Name": "RE",
|
||||||
"ThreeLetterISORegionName": "REU",
|
"ThreeLetterISORegionName": "REU",
|
||||||
"TwoLetterISORegionName": "RE"
|
"TwoLetterISORegionName": "RE"
|
||||||
|
|||||||
@@ -349,7 +349,8 @@ pli||pi|Pali|pali
|
|||||||
pol||pl|Polish|polonais
|
pol||pl|Polish|polonais
|
||||||
pon|||Pohnpeian|pohnpei
|
pon|||Pohnpeian|pohnpei
|
||||||
por||pt|Portuguese|portugais
|
por||pt|Portuguese|portugais
|
||||||
pob||pt-br|Portuguese (Brazil)|portugais
|
pop||pt-pt|Portuguese (Portugal)|portugais (pt-pt)
|
||||||
|
pob||pt-br|Portuguese (Brazil)|portugais (pt-br)
|
||||||
pra|||Prakrit languages|prâkrit, langues
|
pra|||Prakrit languages|prâkrit, langues
|
||||||
pro|||Provençal, Old (to 1500)|provençal ancien (jusqu'à 1500)
|
pro|||Provençal, Old (to 1500)|provençal ancien (jusqu'à 1500)
|
||||||
pus||ps|Pushto; Pashto|pachto
|
pus||ps|Pushto; Pashto|pachto
|
||||||
|
|||||||
@@ -126,7 +126,8 @@ namespace Emby.Server.Implementations.Plugins
|
|||||||
{
|
{
|
||||||
assembly = Assembly.LoadFrom(file);
|
assembly = Assembly.LoadFrom(file);
|
||||||
|
|
||||||
assembly.GetExportedTypes();
|
// Load all required types to verify that the plugin will load
|
||||||
|
assembly.GetTypes();
|
||||||
}
|
}
|
||||||
catch (FileLoadException ex)
|
catch (FileLoadException ex)
|
||||||
{
|
{
|
||||||
@@ -134,7 +135,7 @@ namespace Emby.Server.Implementations.Plugins
|
|||||||
ChangePluginState(plugin, PluginStatus.Malfunctioned);
|
ChangePluginState(plugin, PluginStatus.Malfunctioned);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
catch (TypeLoadException ex) // Undocumented exception
|
catch (SystemException ex) when (ex is TypeLoadException or ReflectionTypeLoadException) // Undocumented exception
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Failed to load assembly {Path}. This error occurs when a plugin references an incompatible version of one of the shared libraries. Disabling plugin.", file);
|
_logger.LogError(ex, "Failed to load assembly {Path}. This error occurs when a plugin references an incompatible version of one of the shared libraries. Disabling plugin.", file);
|
||||||
ChangePluginState(plugin, PluginStatus.NotSupported);
|
ChangePluginState(plugin, PluginStatus.NotSupported);
|
||||||
|
|||||||
@@ -638,7 +638,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation(Name + ": Cancelling");
|
_logger.LogInformation("{Name}: Cancelling", Name);
|
||||||
token.Cancel();
|
token.Cancel();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -652,16 +652,16 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation(Name + ": Waiting on Task");
|
_logger.LogInformation("{Name}: Waiting on Task", Name);
|
||||||
var exited = task.Wait(2000);
|
var exited = task.Wait(2000);
|
||||||
|
|
||||||
if (exited)
|
if (exited)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(Name + ": Task exited");
|
_logger.LogInformation("{Name}: Task exited", Name);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogInformation(Name + ": Timed out waiting for task to stop");
|
_logger.LogInformation("{Name}: Timed out waiting for task to stop", Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -674,7 +674,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogDebug(Name + ": Disposing CancellationToken");
|
_logger.LogDebug("{Name}: Disposing CancellationToken", Name);
|
||||||
token.Dispose();
|
token.Dispose();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -161,11 +161,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
|||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException ex)
|
catch (UnauthorizedAccessException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error deleting directory {path}", directory);
|
_logger.LogError(ex, "Error deleting directory {Path}", directory);
|
||||||
}
|
}
|
||||||
catch (IOException ex)
|
catch (IOException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error deleting directory {path}", directory);
|
_logger.LogError(ex, "Error deleting directory {Path}", directory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,11 +179,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
|||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException ex)
|
catch (UnauthorizedAccessException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error deleting file {path}", path);
|
_logger.LogError(ex, "Error deleting file {Path}", path);
|
||||||
}
|
}
|
||||||
catch (IOException ex)
|
catch (IOException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error deleting file {path}", path);
|
_logger.LogError(ex, "Error deleting file {Path}", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,11 +141,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
|||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException ex)
|
catch (UnauthorizedAccessException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error deleting directory {path}", directory);
|
_logger.LogError(ex, "Error deleting directory {Path}", directory);
|
||||||
}
|
}
|
||||||
catch (IOException ex)
|
catch (IOException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error deleting directory {path}", directory);
|
_logger.LogError(ex, "Error deleting directory {Path}", directory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,11 +159,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
|||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException ex)
|
catch (UnauthorizedAccessException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error deleting file {path}", path);
|
_logger.LogError(ex, "Error deleting file {Path}", path);
|
||||||
}
|
}
|
||||||
catch (IOException ex)
|
catch (IOException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error deleting file {path}", path);
|
_logger.LogError(ex, "Error deleting file {Path}", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -571,7 +571,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version));
|
?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version));
|
||||||
|
|
||||||
await PerformPackageInstallation(package, plugin?.Manifest.Status ?? PluginStatus.Active, cancellationToken).ConfigureAwait(false);
|
await PerformPackageInstallation(package, plugin?.Manifest.Status ?? PluginStatus.Active, cancellationToken).ConfigureAwait(false);
|
||||||
_logger.LogInformation(plugin == null ? "New plugin installed: {PluginName} {PluginVersion}" : "Plugin updated: {PluginName} {PluginVersion}", package.Name, package.Version);
|
_logger.LogInformation("Plugin {Action}: {PluginName} {PluginVersion}", plugin == null ? "installed" : "updated", package.Name, package.Version);
|
||||||
|
|
||||||
return plugin != null;
|
return plugin != null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ namespace Jellyfin.Api.Auth
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var authorizationInfo = await _authService.Authenticate(Request).ConfigureAwait(false);
|
var authorizationInfo = await _authService.Authenticate(Request).ConfigureAwait(false);
|
||||||
|
if (!authorizationInfo.HasToken)
|
||||||
|
{
|
||||||
|
return AuthenticateResult.NoResult();
|
||||||
|
}
|
||||||
|
|
||||||
var role = UserRoles.User;
|
var role = UserRoles.User;
|
||||||
if (authorizationInfo.IsApiKey || authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator))
|
if (authorizationInfo.IsApiKey || authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net.Mime;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Api.Attributes;
|
||||||
|
using Jellyfin.Api.Constants;
|
||||||
|
using Jellyfin.Api.Helpers;
|
||||||
|
using Jellyfin.Api.Models.ClientLogDtos;
|
||||||
|
using MediaBrowser.Controller.ClientEvent;
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using MediaBrowser.Model.ClientLog;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Client log controller.
|
||||||
|
/// </summary>
|
||||||
|
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||||
|
public class ClientLogController : BaseJellyfinApiController
|
||||||
|
{
|
||||||
|
private const int MaxDocumentSize = 1_000_000;
|
||||||
|
private readonly IClientEventLogger _clientEventLogger;
|
||||||
|
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ClientLogController"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientEventLogger">Instance of the <see cref="IClientEventLogger"/> interface.</param>
|
||||||
|
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
||||||
|
public ClientLogController(
|
||||||
|
IClientEventLogger clientEventLogger,
|
||||||
|
IServerConfigurationManager serverConfigurationManager)
|
||||||
|
{
|
||||||
|
_clientEventLogger = clientEventLogger;
|
||||||
|
_serverConfigurationManager = serverConfigurationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Post event from client.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientLogEventDto">The client log dto.</param>
|
||||||
|
/// <response code="204">Event logged.</response>
|
||||||
|
/// <response code="403">Event logging disabled.</response>
|
||||||
|
/// <returns>Submission status.</returns>
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public ActionResult LogEvent([FromBody] ClientLogEventDto clientLogEventDto)
|
||||||
|
{
|
||||||
|
if (!_serverConfigurationManager.Configuration.AllowClientLogUpload)
|
||||||
|
{
|
||||||
|
return Forbid();
|
||||||
|
}
|
||||||
|
|
||||||
|
var (clientName, clientVersion, userId, deviceId) = GetRequestInformation();
|
||||||
|
Log(clientLogEventDto, userId, clientName, clientVersion, deviceId);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bulk post events from client.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientLogEventDtos">The list of client log dtos.</param>
|
||||||
|
/// <response code="204">All events logged.</response>
|
||||||
|
/// <response code="403">Event logging disabled.</response>
|
||||||
|
/// <returns>Submission status.</returns>
|
||||||
|
[HttpPost("Bulk")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public ActionResult LogEvents([FromBody] ClientLogEventDto[] clientLogEventDtos)
|
||||||
|
{
|
||||||
|
if (!_serverConfigurationManager.Configuration.AllowClientLogUpload)
|
||||||
|
{
|
||||||
|
return Forbid();
|
||||||
|
}
|
||||||
|
|
||||||
|
var (clientName, clientVersion, userId, deviceId) = GetRequestInformation();
|
||||||
|
foreach (var dto in clientLogEventDtos)
|
||||||
|
{
|
||||||
|
Log(dto, userId, clientName, clientVersion, deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Upload a document.
|
||||||
|
/// </summary>
|
||||||
|
/// <response code="200">Document saved.</response>
|
||||||
|
/// <response code="403">Event logging disabled.</response>
|
||||||
|
/// <response code="413">Upload size too large.</response>
|
||||||
|
/// <returns>Create response.</returns>
|
||||||
|
[HttpPost("Document")]
|
||||||
|
[ProducesResponseType(typeof(ClientLogDocumentResponseDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status413PayloadTooLarge)]
|
||||||
|
[AcceptsFile(MediaTypeNames.Text.Plain)]
|
||||||
|
[RequestSizeLimit(MaxDocumentSize)]
|
||||||
|
public async Task<ActionResult<ClientLogDocumentResponseDto>> LogFile()
|
||||||
|
{
|
||||||
|
if (!_serverConfigurationManager.Configuration.AllowClientLogUpload)
|
||||||
|
{
|
||||||
|
return Forbid();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Request.ContentLength > MaxDocumentSize)
|
||||||
|
{
|
||||||
|
// Manually validate to return proper status code.
|
||||||
|
return StatusCode(StatusCodes.Status413PayloadTooLarge, $"Payload must be less than {MaxDocumentSize:N0} bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
var (clientName, clientVersion, _, _) = GetRequestInformation();
|
||||||
|
var fileName = await _clientEventLogger.WriteDocumentAsync(clientName, clientVersion, Request.Body)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
return Ok(new ClientLogDocumentResponseDto(fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Log(
|
||||||
|
ClientLogEventDto dto,
|
||||||
|
Guid userId,
|
||||||
|
string clientName,
|
||||||
|
string clientVersion,
|
||||||
|
string deviceId)
|
||||||
|
{
|
||||||
|
_clientEventLogger.Log(new ClientLogEvent(
|
||||||
|
dto.Timestamp,
|
||||||
|
dto.Level,
|
||||||
|
userId,
|
||||||
|
clientName,
|
||||||
|
clientVersion,
|
||||||
|
deviceId,
|
||||||
|
dto.Message));
|
||||||
|
}
|
||||||
|
|
||||||
|
private (string ClientName, string ClientVersion, Guid UserId, string DeviceId) GetRequestInformation()
|
||||||
|
{
|
||||||
|
var clientName = ClaimHelpers.GetClient(HttpContext.User) ?? "unknown-client";
|
||||||
|
var clientVersion = ClaimHelpers.GetIsApiKey(HttpContext.User)
|
||||||
|
? "apikey"
|
||||||
|
: ClaimHelpers.GetVersion(HttpContext.User) ?? "unknown-version";
|
||||||
|
var userId = ClaimHelpers.GetUserId(HttpContext.User) ?? Guid.Empty;
|
||||||
|
var deviceId = ClaimHelpers.GetDeviceId(HttpContext.User) ?? "unknown-device-id";
|
||||||
|
|
||||||
|
return (clientName, clientVersion, userId, deviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ using Jellyfin.Data.Entities;
|
|||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Dto;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@@ -143,21 +143,24 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
existingDisplayPreferences.ScrollDirection = displayPreferences.ScrollDirection;
|
existingDisplayPreferences.ScrollDirection = displayPreferences.ScrollDirection;
|
||||||
existingDisplayPreferences.ChromecastVersion = displayPreferences.CustomPrefs.TryGetValue("chromecastVersion", out var chromecastVersion)
|
existingDisplayPreferences.ChromecastVersion = displayPreferences.CustomPrefs.TryGetValue("chromecastVersion", out var chromecastVersion)
|
||||||
|
&& !string.IsNullOrEmpty(chromecastVersion)
|
||||||
? Enum.Parse<ChromecastVersion>(chromecastVersion, true)
|
? Enum.Parse<ChromecastVersion>(chromecastVersion, true)
|
||||||
: ChromecastVersion.Stable;
|
: ChromecastVersion.Stable;
|
||||||
displayPreferences.CustomPrefs.Remove("chromecastVersion");
|
displayPreferences.CustomPrefs.Remove("chromecastVersion");
|
||||||
|
|
||||||
existingDisplayPreferences.EnableNextVideoInfoOverlay = displayPreferences.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enableNextVideoInfoOverlay)
|
existingDisplayPreferences.EnableNextVideoInfoOverlay = !displayPreferences.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enableNextVideoInfoOverlay)
|
||||||
? bool.Parse(enableNextVideoInfoOverlay)
|
|| string.IsNullOrEmpty(enableNextVideoInfoOverlay)
|
||||||
: true;
|
|| bool.Parse(enableNextVideoInfoOverlay);
|
||||||
displayPreferences.CustomPrefs.Remove("enableNextVideoInfoOverlay");
|
displayPreferences.CustomPrefs.Remove("enableNextVideoInfoOverlay");
|
||||||
|
|
||||||
existingDisplayPreferences.SkipBackwardLength = displayPreferences.CustomPrefs.TryGetValue("skipBackLength", out var skipBackLength)
|
existingDisplayPreferences.SkipBackwardLength = displayPreferences.CustomPrefs.TryGetValue("skipBackLength", out var skipBackLength)
|
||||||
|
&& !string.IsNullOrEmpty(skipBackLength)
|
||||||
? int.Parse(skipBackLength, CultureInfo.InvariantCulture)
|
? int.Parse(skipBackLength, CultureInfo.InvariantCulture)
|
||||||
: 10000;
|
: 10000;
|
||||||
displayPreferences.CustomPrefs.Remove("skipBackLength");
|
displayPreferences.CustomPrefs.Remove("skipBackLength");
|
||||||
|
|
||||||
existingDisplayPreferences.SkipForwardLength = displayPreferences.CustomPrefs.TryGetValue("skipForwardLength", out var skipForwardLength)
|
existingDisplayPreferences.SkipForwardLength = displayPreferences.CustomPrefs.TryGetValue("skipForwardLength", out var skipForwardLength)
|
||||||
|
&& !string.IsNullOrEmpty(skipForwardLength)
|
||||||
? int.Parse(skipForwardLength, CultureInfo.InvariantCulture)
|
? int.Parse(skipForwardLength, CultureInfo.InvariantCulture)
|
||||||
: 30000;
|
: 30000;
|
||||||
displayPreferences.CustomPrefs.Remove("skipForwardLength");
|
displayPreferences.CustomPrefs.Remove("skipForwardLength");
|
||||||
@@ -196,7 +199,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
var itemPrefs = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, itemId, existingDisplayPreferences.Client);
|
var itemPrefs = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, itemId, existingDisplayPreferences.Client);
|
||||||
itemPrefs.SortBy = displayPreferences.SortBy;
|
itemPrefs.SortBy = displayPreferences.SortBy ?? "SortName";
|
||||||
itemPrefs.SortOrder = displayPreferences.SortOrder;
|
itemPrefs.SortOrder = displayPreferences.SortOrder;
|
||||||
itemPrefs.RememberIndexing = displayPreferences.RememberIndexing;
|
itemPrefs.RememberIndexing = displayPreferences.RememberIndexing;
|
||||||
itemPrefs.RememberSorting = displayPreferences.RememberSorting;
|
itemPrefs.RememberSorting = displayPreferences.RememberSorting;
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
_dlnaManager.UpdateProfile(deviceProfile);
|
_dlnaManager.UpdateProfile(profileId, deviceProfile);
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1391,7 +1391,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogError("Invalid HLS segment container: " + segmentFormat);
|
_logger.LogError("Invalid HLS segment container: {SegmentFormat}", segmentFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxMuxingQueueSize = _encodingOptions.MaxMuxingQueueSize > 128
|
var maxMuxingQueueSize = _encodingOptions.MaxMuxingQueueSize > 128
|
||||||
@@ -1794,7 +1794,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("Deleting partial HLS file {path}", path);
|
_logger.LogDebug("Deleting partial HLS file {Path}", path);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1802,15 +1802,15 @@ namespace Jellyfin.Api.Controllers
|
|||||||
}
|
}
|
||||||
catch (IOException ex)
|
catch (IOException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error deleting partial stream file(s) {path}", path);
|
_logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
|
||||||
|
|
||||||
var task = Task.Delay(100);
|
var task = Task.Delay(100);
|
||||||
Task.WaitAll(task);
|
task.Wait();
|
||||||
DeleteFile(path, retryCount + 1);
|
DeleteFile(path, retryCount + 1);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error deleting partial stream file(s) {path}", path);
|
_logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
var transcodePath = _serverConfigurationManager.GetTranscodePath();
|
var transcodePath = _serverConfigurationManager.GetTranscodePath();
|
||||||
file = Path.GetFullPath(Path.Combine(transcodePath, file));
|
file = Path.GetFullPath(Path.Combine(transcodePath, file));
|
||||||
var fileDir = Path.GetDirectoryName(file);
|
var fileDir = Path.GetDirectoryName(file);
|
||||||
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath))
|
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath, StringComparison.InvariantCulture))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid segment.");
|
return BadRequest("Invalid segment.");
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
var transcodePath = _serverConfigurationManager.GetTranscodePath();
|
var transcodePath = _serverConfigurationManager.GetTranscodePath();
|
||||||
file = Path.GetFullPath(Path.Combine(transcodePath, file));
|
file = Path.GetFullPath(Path.Combine(transcodePath, file));
|
||||||
var fileDir = Path.GetDirectoryName(file);
|
var fileDir = Path.GetDirectoryName(file);
|
||||||
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath) || Path.GetExtension(file) != ".m3u8")
|
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath, StringComparison.InvariantCulture) || Path.GetExtension(file) != ".m3u8")
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid segment.");
|
return BadRequest("Invalid segment.");
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
file = Path.GetFullPath(Path.Combine(transcodeFolderPath, file));
|
file = Path.GetFullPath(Path.Combine(transcodeFolderPath, file));
|
||||||
var fileDir = Path.GetDirectoryName(file);
|
var fileDir = Path.GetDirectoryName(file);
|
||||||
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodeFolderPath))
|
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodeFolderPath, StringComparison.InvariantCulture))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid segment.");
|
return BadRequest("Invalid segment.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!path.StartsWith(_applicationPaths.GeneralPath))
|
if (!path.StartsWith(_applicationPaths.GeneralPath, StringComparison.InvariantCulture))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid image path.");
|
return BadRequest("Invalid image path.");
|
||||||
}
|
}
|
||||||
@@ -177,7 +177,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
|
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
|
||||||
{
|
{
|
||||||
if (!path.StartsWith(basePath))
|
if (!path.StartsWith(basePath, StringComparison.InvariantCulture))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid image path.");
|
return BadRequest("Invalid image path.");
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
|
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
|
||||||
{
|
{
|
||||||
if (!path.StartsWith(basePath))
|
if (!path.StartsWith(basePath, StringComparison.InvariantCulture))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid image path.");
|
return BadRequest("Invalid image path.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -528,7 +528,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
if (fontFile != null && fileSize != null && fileSize > 0)
|
if (fontFile != null && fileSize != null && fileSize > 0)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Fallback font size is {fileSize} Bytes", fileSize);
|
_logger.LogDebug("Fallback font size is {FileSize} Bytes", fileSize);
|
||||||
return PhysicalFile(fontFile.FullName, MimeTypes.GetMimeType(fontFile.FullName));
|
return PhysicalFile(fontFile.FullName, MimeTypes.GetMimeType(fontFile.FullName));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -212,10 +212,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <returns>An <see cref="IEnumerable{WakeOnLanInfo}"/> with the WakeOnLan infos.</returns>
|
/// <returns>An <see cref="IEnumerable{WakeOnLanInfo}"/> with the WakeOnLan infos.</returns>
|
||||||
[HttpGet("WakeOnLanInfo")]
|
[HttpGet("WakeOnLanInfo")]
|
||||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||||
|
[Obsolete("This endpoint is obsolete.")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
public ActionResult<IEnumerable<WakeOnLanInfo>> GetWakeOnLanInfo()
|
public ActionResult<IEnumerable<WakeOnLanInfo>> GetWakeOnLanInfo()
|
||||||
{
|
{
|
||||||
var result = _appHost.GetWakeOnLanInfo();
|
var result = _network.GetMacAddresses()
|
||||||
|
.Select(i => new WakeOnLanInfo(i))
|
||||||
|
.ToList();
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace Jellyfin.Api.Helpers
|
|||||||
// If any this null throw an exception.
|
// If any this null throw an exception.
|
||||||
if (source == null || destination == null)
|
if (source == null || destination == null)
|
||||||
{
|
{
|
||||||
throw new Exception("Source or/and Destination Objects are null");
|
throw new ArgumentException("Source or/and Destination Objects are null");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getting the Types of the objects.
|
// Getting the Types of the objects.
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ namespace Jellyfin.Api.Helpers
|
|||||||
|
|
||||||
mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId)
|
mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId)
|
||||||
? mediaSources[0]
|
? mediaSources[0]
|
||||||
: mediaSources.Find(i => string.Equals(i.Id, streamingRequest.MediaSourceId, StringComparison.InvariantCulture));
|
: mediaSources.Find(i => string.Equals(i.Id, streamingRequest.MediaSourceId, StringComparison.Ordinal));
|
||||||
|
|
||||||
if (mediaSource == null && Guid.Parse(streamingRequest.MediaSourceId) == streamingRequest.Id)
|
if (mediaSource == null && Guid.Parse(streamingRequest.MediaSourceId) == streamingRequest.Id)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -283,6 +283,7 @@ namespace Jellyfin.Api.Helpers
|
|||||||
|
|
||||||
lock (job.ProcessLock!)
|
lock (job.ProcessLock!)
|
||||||
{
|
{
|
||||||
|
#pragma warning disable CA1849 // Can't await in lock block
|
||||||
job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
|
job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
|
||||||
|
|
||||||
var process = job.Process;
|
var process = job.Process;
|
||||||
@@ -308,6 +309,7 @@ namespace Jellyfin.Api.Helpers
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#pragma warning restore CA1849
|
||||||
}
|
}
|
||||||
|
|
||||||
if (delete(job.Path!))
|
if (delete(job.Path!))
|
||||||
@@ -541,8 +543,7 @@ namespace Jellyfin.Api.Helpers
|
|||||||
state,
|
state,
|
||||||
cancellationTokenSource);
|
cancellationTokenSource);
|
||||||
|
|
||||||
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
_logger.LogInformation("{Filename} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
_logger.LogInformation(commandLineLogMessage);
|
|
||||||
|
|
||||||
var logFilePrefix = "FFmpeg.Transcode-";
|
var logFilePrefix = "FFmpeg.Transcode-";
|
||||||
if (state.VideoRequest != null
|
if (state.VideoRequest != null
|
||||||
@@ -560,8 +561,9 @@ namespace Jellyfin.Api.Helpers
|
|||||||
// FFmpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
|
// FFmpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
|
||||||
Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
|
Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
|
||||||
|
|
||||||
|
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
||||||
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(request.Path + Environment.NewLine + Environment.NewLine + JsonSerializer.Serialize(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(request.Path + Environment.NewLine + Environment.NewLine + JsonSerializer.Serialize(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
||||||
await logStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
|
await logStream.WriteAsync(commandLineLogMessageBytes, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state);
|
process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state);
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.2.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.2.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace Jellyfin.Api.Models.ClientLogDtos
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Client log document response dto.
|
||||||
|
/// </summary>
|
||||||
|
public class ClientLogDocumentResponseDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ClientLogDocumentResponseDto"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">The file name.</param>
|
||||||
|
public ClientLogDocumentResponseDto(string fileName)
|
||||||
|
{
|
||||||
|
FileName = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the resulting filename.
|
||||||
|
/// </summary>
|
||||||
|
public string FileName { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.Models.ClientLogDtos
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The client log dto.
|
||||||
|
/// </summary>
|
||||||
|
public class ClientLogEventDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the event timestamp.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the log level.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public LogLevel Level { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the log message.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -197,7 +197,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("No throttle data for " + path);
|
_logger.LogDebug("No throttle data for {Path}", path);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Code analysers-->
|
<!-- Code analysers-->
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -455,10 +455,10 @@ namespace Jellyfin.Networking.Manager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// No bind address, so return all internal interfaces.
|
// No bind address, so return all internal interfaces.
|
||||||
return CreateCollection(_internalInterfaces.Where(p => !p.IsLoopback()));
|
return CreateCollection(_internalInterfaces);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Collection<IPObject>(_bindAddresses);
|
return new Collection<IPObject>(_bindAddresses.Where(a => IsInLocalNetwork(a)).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -481,7 +481,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// As private addresses can be redefined by Configuration.LocalNetworkAddresses
|
// As private addresses can be redefined by Configuration.LocalNetworkAddresses
|
||||||
return _lanSubnets.ContainsAddress(address) && !_excludedSubnets.ContainsAddress(address);
|
return address.IsLoopback() || (_lanSubnets.ContainsAddress(address) && !_excludedSubnets.ContainsAddress(address));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -647,16 +647,6 @@ namespace Jellyfin.Networking.Manager
|
|||||||
_interfaceAddresses.AddItem(address, false);
|
_interfaceAddresses.AddItem(address, false);
|
||||||
_interfaceNames[parts[2]] = Math.Abs(index);
|
_interfaceNames[parts[2]] = Math.Abs(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsIP4Enabled)
|
|
||||||
{
|
|
||||||
_interfaceAddresses.AddItem(IPNetAddress.IP4Loopback);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsIP6Enabled)
|
|
||||||
{
|
|
||||||
_interfaceAddresses.AddItem(IPNetAddress.IP6Loopback);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InitialiseLAN(config);
|
InitialiseLAN(config);
|
||||||
@@ -1037,17 +1027,14 @@ namespace Jellyfin.Networking.Manager
|
|||||||
// Subnets are the same as the calculated internal interface.
|
// Subnets are the same as the calculated internal interface.
|
||||||
_lanSubnets = new Collection<IPObject>();
|
_lanSubnets = new Collection<IPObject>();
|
||||||
|
|
||||||
// We must listen on loopback for LiveTV to function regardless of the settings.
|
|
||||||
if (IsIP6Enabled)
|
if (IsIP6Enabled)
|
||||||
{
|
{
|
||||||
_lanSubnets.AddItem(IPNetAddress.IP6Loopback);
|
|
||||||
_lanSubnets.AddItem(IPNetAddress.Parse("fc00::/7")); // ULA
|
_lanSubnets.AddItem(IPNetAddress.Parse("fc00::/7")); // ULA
|
||||||
_lanSubnets.AddItem(IPNetAddress.Parse("fe80::/10")); // Site local
|
_lanSubnets.AddItem(IPNetAddress.Parse("fe80::/10")); // Site local
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsIP4Enabled)
|
if (IsIP4Enabled)
|
||||||
{
|
{
|
||||||
_lanSubnets.AddItem(IPNetAddress.IP4Loopback);
|
|
||||||
_lanSubnets.AddItem(IPNetAddress.Parse("10.0.0.0/8"));
|
_lanSubnets.AddItem(IPNetAddress.Parse("10.0.0.0/8"));
|
||||||
_lanSubnets.AddItem(IPNetAddress.Parse("172.16.0.0/12"));
|
_lanSubnets.AddItem(IPNetAddress.Parse("172.16.0.0/12"));
|
||||||
_lanSubnets.AddItem(IPNetAddress.Parse("192.168.0.0/16"));
|
_lanSubnets.AddItem(IPNetAddress.Parse("192.168.0.0/16"));
|
||||||
@@ -1055,17 +1042,6 @@ namespace Jellyfin.Networking.Manager
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// We must listen on loopback for LiveTV to function regardless of the settings.
|
|
||||||
if (IsIP6Enabled)
|
|
||||||
{
|
|
||||||
_lanSubnets.AddItem(IPNetAddress.IP6Loopback);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsIP4Enabled)
|
|
||||||
{
|
|
||||||
_lanSubnets.AddItem(IPNetAddress.IP4Loopback);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal interfaces must be private, not excluded and part of the LocalNetworkSubnet.
|
// Internal interfaces must be private, not excluded and part of the LocalNetworkSubnet.
|
||||||
_internalInterfaces = CreateCollection(_interfaceAddresses.Where(IsInLocalNetwork));
|
_internalInterfaces = CreateCollection(_interfaceAddresses.Where(IsInLocalNetwork));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ namespace Jellyfin.Server.Implementations.Events
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError(e, "Uncaught exception in EventConsumer {type}: ", service.GetType());
|
_logger.LogError(e, "Uncaught exception in EventConsumer {Type}: ", service.GetType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,13 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
|
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0-rc.2*">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0-rc.2*">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -185,9 +185,21 @@ namespace Jellyfin.Server.Implementations.Security
|
|||||||
authInfo.IsAuthenticated = true;
|
authInfo.IsAuthenticated = true;
|
||||||
authInfo.Client = key.Name;
|
authInfo.Client = key.Name;
|
||||||
authInfo.Token = key.AccessToken;
|
authInfo.Token = key.AccessToken;
|
||||||
authInfo.DeviceId = string.Empty;
|
if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
|
||||||
authInfo.Device = string.Empty;
|
{
|
||||||
authInfo.Version = string.Empty;
|
authInfo.DeviceId = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(authInfo.Device))
|
||||||
|
{
|
||||||
|
authInfo.Device = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(authInfo.Version))
|
||||||
|
{
|
||||||
|
authInfo.Version = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
authInfo.IsApiKey = true;
|
authInfo.IsApiKey = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ using MediaBrowser.Controller.Library;
|
|||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Security;
|
using MediaBrowser.Controller.Security;
|
||||||
using MediaBrowser.Model.Activity;
|
using MediaBrowser.Model.Activity;
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -42,67 +41,61 @@ namespace Jellyfin.Server
|
|||||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
|
/// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||||
/// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
|
/// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||||
/// <param name="startupConfig">The <see cref="IConfiguration" /> to be used by the <see cref="CoreAppHost" />.</param>
|
/// <param name="startupConfig">The <see cref="IConfiguration" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||||
/// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
|
|
||||||
/// <param name="collection">The <see cref="IServiceCollection"/> to be used by the <see cref="CoreAppHost"/>.</param>
|
|
||||||
public CoreAppHost(
|
public CoreAppHost(
|
||||||
IServerApplicationPaths applicationPaths,
|
IServerApplicationPaths applicationPaths,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IStartupOptions options,
|
IStartupOptions options,
|
||||||
IConfiguration startupConfig,
|
IConfiguration startupConfig)
|
||||||
IFileSystem fileSystem,
|
|
||||||
IServiceCollection collection)
|
|
||||||
: base(
|
: base(
|
||||||
applicationPaths,
|
applicationPaths,
|
||||||
loggerFactory,
|
loggerFactory,
|
||||||
options,
|
options,
|
||||||
startupConfig,
|
startupConfig)
|
||||||
fileSystem,
|
|
||||||
collection)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void RegisterServices()
|
protected override void RegisterServices(IServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
// Register an image encoder
|
// Register an image encoder
|
||||||
bool useSkiaEncoder = SkiaEncoder.IsNativeLibAvailable();
|
bool useSkiaEncoder = SkiaEncoder.IsNativeLibAvailable();
|
||||||
Type imageEncoderType = useSkiaEncoder
|
Type imageEncoderType = useSkiaEncoder
|
||||||
? typeof(SkiaEncoder)
|
? typeof(SkiaEncoder)
|
||||||
: typeof(NullImageEncoder);
|
: typeof(NullImageEncoder);
|
||||||
ServiceCollection.AddSingleton(typeof(IImageEncoder), imageEncoderType);
|
serviceCollection.AddSingleton(typeof(IImageEncoder), imageEncoderType);
|
||||||
|
|
||||||
// Log a warning if the Skia encoder could not be used
|
// Log a warning if the Skia encoder could not be used
|
||||||
if (!useSkiaEncoder)
|
if (!useSkiaEncoder)
|
||||||
{
|
{
|
||||||
Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}.");
|
Logger.LogWarning("Skia not available. Will fallback to {ImageEncoder}.", nameof(NullImageEncoder));
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceCollection.AddDbContextPool<JellyfinDb>(
|
serviceCollection.AddDbContextPool<JellyfinDb>(
|
||||||
options => options
|
options => options
|
||||||
.UseLoggerFactory(LoggerFactory)
|
.UseLoggerFactory(LoggerFactory)
|
||||||
.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"));
|
.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"));
|
||||||
|
|
||||||
ServiceCollection.AddEventServices();
|
serviceCollection.AddEventServices();
|
||||||
ServiceCollection.AddSingleton<IBaseItemManager, BaseItemManager>();
|
serviceCollection.AddSingleton<IBaseItemManager, BaseItemManager>();
|
||||||
ServiceCollection.AddSingleton<IEventManager, EventManager>();
|
serviceCollection.AddSingleton<IEventManager, EventManager>();
|
||||||
ServiceCollection.AddSingleton<JellyfinDbProvider>();
|
serviceCollection.AddSingleton<JellyfinDbProvider>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IActivityManager, ActivityManager>();
|
serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
|
||||||
ServiceCollection.AddSingleton<IUserManager, UserManager>();
|
serviceCollection.AddSingleton<IUserManager, UserManager>();
|
||||||
ServiceCollection.AddSingleton<IDisplayPreferencesManager, DisplayPreferencesManager>();
|
serviceCollection.AddSingleton<IDisplayPreferencesManager, DisplayPreferencesManager>();
|
||||||
ServiceCollection.AddSingleton<IDeviceManager, DeviceManager>();
|
serviceCollection.AddSingleton<IDeviceManager, DeviceManager>();
|
||||||
|
|
||||||
// TODO search the assemblies instead of adding them manually?
|
// TODO search the assemblies instead of adding them manually?
|
||||||
ServiceCollection.AddSingleton<IWebSocketListener, SessionWebSocketListener>();
|
serviceCollection.AddSingleton<IWebSocketListener, SessionWebSocketListener>();
|
||||||
ServiceCollection.AddSingleton<IWebSocketListener, ActivityLogWebSocketListener>();
|
serviceCollection.AddSingleton<IWebSocketListener, ActivityLogWebSocketListener>();
|
||||||
ServiceCollection.AddSingleton<IWebSocketListener, ScheduledTasksWebSocketListener>();
|
serviceCollection.AddSingleton<IWebSocketListener, ScheduledTasksWebSocketListener>();
|
||||||
ServiceCollection.AddSingleton<IWebSocketListener, SessionInfoWebSocketListener>();
|
serviceCollection.AddSingleton<IWebSocketListener, SessionInfoWebSocketListener>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
|
serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
|
||||||
|
|
||||||
ServiceCollection.AddScoped<IAuthenticationManager, AuthenticationManager>();
|
serviceCollection.AddScoped<IAuthenticationManager, AuthenticationManager>();
|
||||||
|
|
||||||
base.RegisterServices();
|
base.RegisterServices(serviceCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -416,6 +416,18 @@ namespace Jellyfin.Server.Extensions
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Support dictionary with nullable string value.
|
||||||
|
options.MapType<Dictionary<string, string?>>(() =>
|
||||||
|
new OpenApiSchema
|
||||||
|
{
|
||||||
|
Type = "object",
|
||||||
|
AdditionalProperties = new OpenApiSchema
|
||||||
|
{
|
||||||
|
Type = "string",
|
||||||
|
Nullable = true
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,10 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.0" />
|
||||||
<PackageReference Include="prometheus-net" Version="5.0.1" />
|
<PackageReference Include="prometheus-net" Version="5.0.1" />
|
||||||
<PackageReference Include="prometheus-net.AspNetCore" Version="5.0.1" />
|
<PackageReference Include="prometheus-net.AspNetCore" Version="5.0.1" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
|
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
|
||||||
@@ -44,7 +44,8 @@
|
|||||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Graylog" Version="2.2.2" />
|
<PackageReference Include="Serilog.Sinks.Graylog" Version="2.2.2" />
|
||||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.6" />
|
<PackageReference Include="Serilog.Sinks.Map" Version="1.0.2" />
|
||||||
|
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.7" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ namespace Jellyfin.Server.Middleware
|
|||||||
if (_enableWarning && watch.ElapsedMilliseconds > _warningThreshold)
|
if (_enableWarning && watch.ElapsedMilliseconds > _warningThreshold)
|
||||||
{
|
{
|
||||||
_logger.LogWarning(
|
_logger.LogWarning(
|
||||||
"Slow HTTP Response from {url} to {remoteIp} in {elapsed:g} with Status Code {statusCode}",
|
"Slow HTTP Response from {Url} to {RemoteIp} in {Elapsed:g} with Status Code {StatusCode}",
|
||||||
context.Request.GetDisplayUrl(),
|
context.Request.GetDisplayUrl(),
|
||||||
context.GetNormalizedRemoteIp(),
|
context.GetNormalizedRemoteIp(),
|
||||||
watch.Elapsed,
|
watch.Elapsed,
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ namespace Jellyfin.Server.Middleware
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!key.Contains('='))
|
if (!key.Contains('=', StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
_store = value;
|
_store = value;
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ using Jellyfin.Data.Enums;
|
|||||||
using Jellyfin.Server.Implementations;
|
using Jellyfin.Server.Implementations;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Dto;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SQLitePCL.pretty;
|
using SQLitePCL.pretty;
|
||||||
|
|
||||||
@@ -114,6 +114,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||||||
}
|
}
|
||||||
|
|
||||||
var chromecastVersion = dto.CustomPrefs.TryGetValue("chromecastVersion", out var version)
|
var chromecastVersion = dto.CustomPrefs.TryGetValue("chromecastVersion", out var version)
|
||||||
|
&& !string.IsNullOrEmpty(version)
|
||||||
? chromecastDict[version]
|
? chromecastDict[version]
|
||||||
: ChromecastVersion.Stable;
|
: ChromecastVersion.Stable;
|
||||||
dto.CustomPrefs.Remove("chromecastVersion");
|
dto.CustomPrefs.Remove("chromecastVersion");
|
||||||
|
|||||||
+59
-32
@@ -10,10 +10,10 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommandLine;
|
using CommandLine;
|
||||||
using Emby.Server.Implementations;
|
using Emby.Server.Implementations;
|
||||||
using Emby.Server.Implementations.IO;
|
|
||||||
using Jellyfin.Server.Implementations;
|
using Jellyfin.Server.Implementations;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller.ClientEvent;
|
||||||
using MediaBrowser.Controller.Extensions;
|
using MediaBrowser.Controller.Extensions;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
@@ -26,6 +26,7 @@ using Microsoft.Extensions.Logging;
|
|||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Extensions.Logging;
|
using Serilog.Extensions.Logging;
|
||||||
|
using Serilog.Filters;
|
||||||
using SQLitePCL;
|
using SQLitePCL;
|
||||||
using ConfigurationExtensions = MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
using ConfigurationExtensions = MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
||||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
@@ -157,34 +158,36 @@ namespace Jellyfin.Server
|
|||||||
|
|
||||||
ApplicationHost.LogEnvironmentInfo(_logger, appPaths);
|
ApplicationHost.LogEnvironmentInfo(_logger, appPaths);
|
||||||
|
|
||||||
|
// If hosting the web client, validate the client content path
|
||||||
|
if (startupConfig.HostWebClient())
|
||||||
|
{
|
||||||
|
string? webContentPath = appPaths.WebPath;
|
||||||
|
if (!Directory.Exists(webContentPath) || !Directory.EnumerateFiles(webContentPath).Any())
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
"The server is expected to host the web client, but the provided content directory is either " +
|
||||||
|
"invalid or empty: {WebContentPath}. If you do not want to host the web client with the " +
|
||||||
|
"server, you may set the '--nowebclient' command line flag, or set" +
|
||||||
|
"'{ConfigKey}=false' in your config settings.",
|
||||||
|
webContentPath,
|
||||||
|
ConfigurationExtensions.HostWebClientKey);
|
||||||
|
Environment.ExitCode = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PerformStaticInitialization();
|
PerformStaticInitialization();
|
||||||
var serviceCollection = new ServiceCollection();
|
|
||||||
|
|
||||||
var appHost = new CoreAppHost(
|
var appHost = new CoreAppHost(
|
||||||
appPaths,
|
appPaths,
|
||||||
_loggerFactory,
|
_loggerFactory,
|
||||||
options,
|
options,
|
||||||
startupConfig,
|
startupConfig);
|
||||||
new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
|
|
||||||
serviceCollection);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// If hosting the web client, validate the client content path
|
var serviceCollection = new ServiceCollection();
|
||||||
if (startupConfig.HostWebClient())
|
appHost.Init(serviceCollection);
|
||||||
{
|
|
||||||
string? webContentPath = appHost.ConfigurationManager.ApplicationPaths.WebPath;
|
|
||||||
if (!Directory.Exists(webContentPath) || Directory.GetFiles(webContentPath).Length == 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"The server is expected to host the web client, but the provided content directory is either " +
|
|
||||||
$"invalid or empty: {webContentPath}. If you do not want to host the web client with the " +
|
|
||||||
"server, you may set the '--nowebclient' command line flag, or set" +
|
|
||||||
$"'{ConfigurationExtensions.HostWebClientKey}=false' in your config settings.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
appHost.Init();
|
|
||||||
|
|
||||||
var webHost = new WebHostBuilder().ConfigureWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build();
|
var webHost = new WebHostBuilder().ConfigureWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build();
|
||||||
|
|
||||||
@@ -595,22 +598,46 @@ namespace Jellyfin.Server
|
|||||||
{
|
{
|
||||||
// Serilog.Log is used by SerilogLoggerFactory when no logger is specified
|
// Serilog.Log is used by SerilogLoggerFactory when no logger is specified
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
.ReadFrom.Configuration(configuration)
|
.WriteTo.Logger(lc =>
|
||||||
.Enrich.FromLogContext()
|
lc.ReadFrom.Configuration(configuration)
|
||||||
.Enrich.WithThreadId()
|
.Enrich.FromLogContext()
|
||||||
|
.Enrich.WithThreadId()
|
||||||
|
.Filter.ByExcluding(Matching.FromSource<ClientEventLogger>()))
|
||||||
|
.WriteTo.Logger(lc =>
|
||||||
|
lc.WriteTo.Map(
|
||||||
|
"ClientName",
|
||||||
|
(clientName, wt)
|
||||||
|
=> wt.File(
|
||||||
|
Path.Combine(appPaths.LogDirectoryPath, "log_" + clientName + "_.log"),
|
||||||
|
rollingInterval: RollingInterval.Day,
|
||||||
|
outputTemplate: "{Message:l}{NewLine}{Exception}",
|
||||||
|
encoding: Encoding.UTF8))
|
||||||
|
.Filter.ByIncludingOnly(Matching.FromSource<ClientEventLogger>()))
|
||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}")
|
.WriteTo.Logger(lc =>
|
||||||
.WriteTo.Async(x => x.File(
|
lc.WriteTo.Async(x => x.File(
|
||||||
Path.Combine(appPaths.LogDirectoryPath, "log_.log"),
|
Path.Combine(appPaths.LogDirectoryPath, "log_.log"),
|
||||||
rollingInterval: RollingInterval.Day,
|
rollingInterval: RollingInterval.Day,
|
||||||
outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}",
|
outputTemplate: "{Message:l}{NewLine}{Exception}",
|
||||||
encoding: Encoding.UTF8))
|
encoding: Encoding.UTF8))
|
||||||
.Enrich.FromLogContext()
|
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}")
|
||||||
.Enrich.WithThreadId()
|
.Enrich.FromLogContext()
|
||||||
|
.Enrich.WithThreadId())
|
||||||
|
.WriteTo.Logger(lc =>
|
||||||
|
lc
|
||||||
|
.WriteTo.Map(
|
||||||
|
"ClientName",
|
||||||
|
(clientName, wt)
|
||||||
|
=> wt.File(
|
||||||
|
Path.Combine(appPaths.LogDirectoryPath, "log_" + clientName + "_.log"),
|
||||||
|
rollingInterval: RollingInterval.Day,
|
||||||
|
outputTemplate: "{Message:l}{NewLine}{Exception}",
|
||||||
|
encoding: Encoding.UTF8))
|
||||||
|
.Filter.ByIncludingOnly(Matching.FromSource<ClientEventLogger>()))
|
||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
|
||||||
Log.Logger.Fatal(ex, "Failed to create/read logger configuration");
|
Log.Logger.Fatal(ex, "Failed to create/read logger configuration");
|
||||||
@@ -648,7 +675,7 @@ namespace Jellyfin.Server
|
|||||||
|
|
||||||
private static string NormalizeCommandLineArgument(string arg)
|
private static string NormalizeCommandLineArgument(string arg)
|
||||||
{
|
{
|
||||||
if (!arg.Contains(" ", StringComparison.OrdinalIgnoreCase))
|
if (!arg.Contains(' ', StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return arg;
|
return arg;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace MediaBrowser.Common
|
namespace MediaBrowser.Common
|
||||||
{
|
{
|
||||||
@@ -137,13 +138,7 @@ namespace MediaBrowser.Common
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes this instance.
|
/// Initializes this instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Init();
|
/// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param>
|
||||||
|
void Init(IServiceCollection serviceCollection);
|
||||||
/// <summary>
|
|
||||||
/// Creates the instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The type.</param>
|
|
||||||
/// <returns>System.Object.</returns>
|
|
||||||
object CreateInstance(Type type);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.ClientLog;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Controller.ClientEvent
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class ClientEventLogger : IClientEventLogger
|
||||||
|
{
|
||||||
|
private const string LogString = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level}] [{ClientName}:{ClientVersion}]: UserId: {UserId} DeviceId: {DeviceId}{NewLine}{Message}";
|
||||||
|
private readonly ILogger<ClientEventLogger> _logger;
|
||||||
|
private readonly IServerApplicationPaths _applicationPaths;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ClientEventLogger"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">Instance of the <see cref="ILogger{ClientEventLogger}"/> interface.</param>
|
||||||
|
/// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param>
|
||||||
|
public ClientEventLogger(
|
||||||
|
ILogger<ClientEventLogger> logger,
|
||||||
|
IServerApplicationPaths applicationPaths)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_applicationPaths = applicationPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Log(ClientLogEvent clientLogEvent)
|
||||||
|
{
|
||||||
|
_logger.Log(
|
||||||
|
LogLevel.Critical,
|
||||||
|
LogString,
|
||||||
|
clientLogEvent.Timestamp,
|
||||||
|
clientLogEvent.Level.ToString(),
|
||||||
|
clientLogEvent.ClientName,
|
||||||
|
clientLogEvent.ClientVersion,
|
||||||
|
clientLogEvent.UserId ?? Guid.Empty,
|
||||||
|
clientLogEvent.DeviceId,
|
||||||
|
Environment.NewLine,
|
||||||
|
clientLogEvent.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<string> WriteDocumentAsync(string clientName, string clientVersion, Stream fileContents)
|
||||||
|
{
|
||||||
|
var fileName = $"upload_{clientName}_{clientVersion}_{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid():N}.log";
|
||||||
|
var logFilePath = Path.Combine(_applicationPaths.LogDirectoryPath, fileName);
|
||||||
|
await using var fileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
|
||||||
|
await fileContents.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
|
using MediaBrowser.Model.ClientLog;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Controller.ClientEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The client event logger.
|
||||||
|
/// </summary>
|
||||||
|
public interface IClientEventLogger
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Logs the event from the client.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientLogEvent">The client log event.</param>
|
||||||
|
void Log(ClientLogEvent clientLogEvent);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a file to the log directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientName">The client name writing the document.</param>
|
||||||
|
/// <param name="clientVersion">The client version writing the document.</param>
|
||||||
|
/// <param name="fileContents">The file contents to write.</param>
|
||||||
|
/// <returns>The created file name.</returns>
|
||||||
|
Task<string> WriteDocumentAsync(
|
||||||
|
string clientName,
|
||||||
|
string clientVersion,
|
||||||
|
Stream fileContents);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,8 +37,9 @@ namespace MediaBrowser.Controller.Dlna
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the profile.
|
/// Updates the profile.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="profileId">The profile id.</param>
|
||||||
/// <param name="profile">The profile.</param>
|
/// <param name="profile">The profile.</param>
|
||||||
void UpdateProfile(DeviceProfile profile);
|
void UpdateProfile(string profileId, DeviceProfile profile);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the profile.
|
/// Deletes the profile.
|
||||||
|
|||||||
@@ -84,8 +84,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
Model.Entities.ExtraType.Scene
|
Model.Entities.ExtraType.Scene
|
||||||
};
|
};
|
||||||
|
|
||||||
public static readonly char[] SlugReplaceChars = { '?', '/', '&' };
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The supported extra folder names and types. See <see cref="Emby.Naming.Common.NamingOptions" />.
|
/// The supported extra folder names and types. See <see cref="Emby.Naming.Common.NamingOptions" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -354,11 +352,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
// if (IsOffline)
|
|
||||||
// {
|
|
||||||
// return LocationType.Offline;
|
|
||||||
// }
|
|
||||||
|
|
||||||
var path = Path;
|
var path = Path;
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
@@ -391,7 +384,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
}
|
}
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public bool IsFileProtocol => IsPathProtocol(MediaProtocol.File);
|
public bool IsFileProtocol => PathProtocol == MediaProtocol.File;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public bool HasPathProtocol => PathProtocol.HasValue;
|
public bool HasPathProtocol => PathProtocol.HasValue;
|
||||||
@@ -583,14 +576,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
}
|
}
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public virtual Guid DisplayParentId
|
public virtual Guid DisplayParentId => ParentId;
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var parentId = ParentId;
|
|
||||||
return parentId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public BaseItem DisplayParent
|
public BaseItem DisplayParent
|
||||||
@@ -853,13 +839,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
return Id.ToString("N", CultureInfo.InvariantCulture);
|
return Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPathProtocol(MediaProtocol protocol)
|
|
||||||
{
|
|
||||||
var current = PathProtocol;
|
|
||||||
|
|
||||||
return current.HasValue && current.Value == protocol;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Tuple<StringBuilder, bool>> GetSortChunks(string s1)
|
private List<Tuple<StringBuilder, bool>> GetSortChunks(string s1)
|
||||||
{
|
{
|
||||||
var list = new List<Tuple<StringBuilder, bool>>();
|
var list = new List<Tuple<StringBuilder, bool>>();
|
||||||
@@ -987,7 +966,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
ReadOnlySpan<char> idString = Id.ToString("N", CultureInfo.InvariantCulture);
|
ReadOnlySpan<char> idString = Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
return System.IO.Path.Join(basePath, "library", idString.Slice(0, 2), idString);
|
return System.IO.Path.Join(basePath, "library", idString[..2], idString);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1302,8 +1281,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
terms.Add(item.Name);
|
terms.Add(item.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
var video = item as Video;
|
if (item is Video video)
|
||||||
if (video != null)
|
|
||||||
{
|
{
|
||||||
if (video.Video3DFormat.HasValue)
|
if (video.Video3DFormat.HasValue)
|
||||||
{
|
{
|
||||||
@@ -1338,7 +1316,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Join('/', terms.ToArray());
|
return string.Join('/', terms);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1361,9 +1339,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
.Select(audio =>
|
.Select(audio =>
|
||||||
{
|
{
|
||||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||||
var dbItem = LibraryManager.GetItemById(audio.Id) as Audio.Audio;
|
if (LibraryManager.GetItemById(audio.Id) is Audio.Audio dbItem)
|
||||||
|
|
||||||
if (dbItem != null)
|
|
||||||
{
|
{
|
||||||
audio = dbItem;
|
audio = dbItem;
|
||||||
}
|
}
|
||||||
@@ -1476,7 +1452,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Error refreshing owned items for {path}", Path ?? Name);
|
Logger.LogError(ex, "Error refreshing owned items for {Path}", Path ?? Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1570,8 +1546,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasTrailers = this as IHasTrailers;
|
if (this is IHasTrailers hasTrailers)
|
||||||
if (hasTrailers != null)
|
|
||||||
{
|
{
|
||||||
localTrailersChanged = await RefreshLocalTrailers(hasTrailers, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
localTrailersChanged = await RefreshLocalTrailers(hasTrailers, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -2268,7 +2243,11 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
var existingImage = GetImageInfo(image.Type, index);
|
var existingImage = GetImageInfo(image.Type, index);
|
||||||
|
|
||||||
if (existingImage != null)
|
if (existingImage == null)
|
||||||
|
{
|
||||||
|
AddImage(image);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
existingImage.Path = image.Path;
|
existingImage.Path = image.Path;
|
||||||
existingImage.DateModified = image.DateModified;
|
existingImage.DateModified = image.DateModified;
|
||||||
@@ -2276,15 +2255,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
existingImage.Height = image.Height;
|
existingImage.Height = image.Height;
|
||||||
existingImage.BlurHash = image.BlurHash;
|
existingImage.BlurHash = image.BlurHash;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
var current = ImageInfos;
|
|
||||||
var currentCount = current.Length;
|
|
||||||
var newArr = new ItemImageInfo[currentCount + 1];
|
|
||||||
current.CopyTo(newArr, 0);
|
|
||||||
newArr[currentCount] = image;
|
|
||||||
ImageInfos = newArr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetImagePath(ImageType type, int index, FileSystemMetadata file)
|
public void SetImagePath(ImageType type, int index, FileSystemMetadata file)
|
||||||
@@ -2298,7 +2268,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
if (image == null)
|
if (image == null)
|
||||||
{
|
{
|
||||||
ImageInfos = ImageInfos.Concat(new[] { GetImageInfo(file, type) }).ToArray();
|
AddImage(GetImageInfo(file, type));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2342,14 +2312,24 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
public void RemoveImage(ItemImageInfo image)
|
public void RemoveImage(ItemImageInfo image)
|
||||||
{
|
{
|
||||||
RemoveImages(new List<ItemImageInfo> { image });
|
RemoveImages(new[] { image });
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveImages(List<ItemImageInfo> deletedImages)
|
public void RemoveImages(IEnumerable<ItemImageInfo> deletedImages)
|
||||||
{
|
{
|
||||||
ImageInfos = ImageInfos.Except(deletedImages).ToArray();
|
ImageInfos = ImageInfos.Except(deletedImages).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddImage(ItemImageInfo image)
|
||||||
|
{
|
||||||
|
var current = ImageInfos;
|
||||||
|
var currentCount = current.Length;
|
||||||
|
var newArr = new ItemImageInfo[currentCount + 1];
|
||||||
|
current.CopyTo(newArr, 0);
|
||||||
|
newArr[currentCount] = image;
|
||||||
|
ImageInfos = newArr;
|
||||||
|
}
|
||||||
|
|
||||||
public virtual Task UpdateToRepositoryAsync(ItemUpdateType updateReason, CancellationToken cancellationToken)
|
public virtual Task UpdateToRepositoryAsync(ItemUpdateType updateReason, CancellationToken cancellationToken)
|
||||||
=> LibraryManager.UpdateItemAsync(this, GetParent(), updateReason, cancellationToken);
|
=> LibraryManager.UpdateItemAsync(this, GetParent(), updateReason, cancellationToken);
|
||||||
|
|
||||||
@@ -2373,7 +2353,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
if (deletedImages.Count > 0)
|
if (deletedImages.Count > 0)
|
||||||
{
|
{
|
||||||
ImageInfos = ImageInfos.Except(deletedImages).ToArray();
|
RemoveImages(deletedImages);
|
||||||
}
|
}
|
||||||
|
|
||||||
return deletedImages.Count > 0;
|
return deletedImages.Count > 0;
|
||||||
@@ -2495,11 +2475,11 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the images.
|
/// Adds the images, updating metadata if they already are part of this item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="imageType">Type of the image.</param>
|
/// <param name="imageType">Type of the image.</param>
|
||||||
/// <param name="images">The images.</param>
|
/// <param name="images">The images.</param>
|
||||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
|
/// <returns><c>true</c> if images were added or updated, <c>false</c> otherwise.</returns>
|
||||||
/// <exception cref="ArgumentException">Cannot call AddImages with chapter images.</exception>
|
/// <exception cref="ArgumentException">Cannot call AddImages with chapter images.</exception>
|
||||||
public bool AddImages(ImageType imageType, List<FileSystemMetadata> images)
|
public bool AddImages(ImageType imageType, List<FileSystemMetadata> images)
|
||||||
{
|
{
|
||||||
@@ -2512,7 +2492,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var newImageList = new List<FileSystemMetadata>();
|
var newImageList = new List<FileSystemMetadata>();
|
||||||
var imageAdded = false;
|
|
||||||
var imageUpdated = false;
|
var imageUpdated = false;
|
||||||
|
|
||||||
foreach (var newImage in images)
|
foreach (var newImage in images)
|
||||||
@@ -2528,7 +2507,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
if (existing == null)
|
if (existing == null)
|
||||||
{
|
{
|
||||||
newImageList.Add(newImage);
|
newImageList.Add(newImage);
|
||||||
imageAdded = true;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2549,19 +2527,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageAdded || images.Count != existingImages.Count)
|
|
||||||
{
|
|
||||||
var newImagePaths = images.Select(i => i.FullName).ToList();
|
|
||||||
|
|
||||||
var deleted = existingImages
|
|
||||||
.FindAll(i => i.IsLocalFile && !newImagePaths.Contains(i.Path.AsSpan(), StringComparison.OrdinalIgnoreCase) && !File.Exists(i.Path));
|
|
||||||
|
|
||||||
if (deleted.Count > 0)
|
|
||||||
{
|
|
||||||
ImageInfos = ImageInfos.Except(deleted).ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newImageList.Count > 0)
|
if (newImageList.Count > 0)
|
||||||
{
|
{
|
||||||
ImageInfos = ImageInfos.Concat(newImageList.Select(i => GetImageInfo(i, imageType))).ToArray();
|
ImageInfos = ImageInfos.Concat(newImageList.Select(i => GetImageInfo(i, imageType))).ToArray();
|
||||||
@@ -2612,7 +2577,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
public bool AllowsMultipleImages(ImageType type)
|
public bool AllowsMultipleImages(ImageType type)
|
||||||
{
|
{
|
||||||
return type == ImageType.Backdrop || type == ImageType.Screenshot || type == ImageType.Chapter;
|
return type == ImageType.Backdrop || type == ImageType.Chapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SwapImagesAsync(ImageType type, int index1, int index2)
|
public Task SwapImagesAsync(ImageType type, int index1, int index2)
|
||||||
@@ -2730,7 +2695,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
protected static string GetMappedPath(BaseItem item, string path, MediaProtocol? protocol)
|
protected static string GetMappedPath(BaseItem item, string path, MediaProtocol? protocol)
|
||||||
{
|
{
|
||||||
if (protocol.HasValue && protocol.Value == MediaProtocol.File)
|
if (protocol == MediaProtocol.File)
|
||||||
{
|
{
|
||||||
return LibraryManager.GetPathAfterNetworkSubstitution(path, item);
|
return LibraryManager.GetPathAfterNetworkSubstitution(path, item);
|
||||||
}
|
}
|
||||||
@@ -2758,8 +2723,10 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
protected Task RefreshMetadataForOwnedItem(BaseItem ownedItem, bool copyTitleMetadata, MetadataRefreshOptions options, CancellationToken cancellationToken)
|
protected Task RefreshMetadataForOwnedItem(BaseItem ownedItem, bool copyTitleMetadata, MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var newOptions = new MetadataRefreshOptions(options);
|
var newOptions = new MetadataRefreshOptions(options)
|
||||||
newOptions.SearchResult = null;
|
{
|
||||||
|
SearchResult = null
|
||||||
|
};
|
||||||
|
|
||||||
var item = this;
|
var item = this;
|
||||||
|
|
||||||
@@ -2820,8 +2787,10 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
protected Task RefreshMetadataForOwnedVideo(MetadataRefreshOptions options, bool copyTitleMetadata, string path, CancellationToken cancellationToken)
|
protected Task RefreshMetadataForOwnedVideo(MetadataRefreshOptions options, bool copyTitleMetadata, string path, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var newOptions = new MetadataRefreshOptions(options);
|
var newOptions = new MetadataRefreshOptions(options)
|
||||||
newOptions.SearchResult = null;
|
{
|
||||||
|
SearchResult = null
|
||||||
|
};
|
||||||
|
|
||||||
var id = LibraryManager.GetNewItemId(path, typeof(Video));
|
var id = LibraryManager.GetNewItemId(path, typeof(Video));
|
||||||
|
|
||||||
@@ -2835,14 +2804,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
newOptions.ForceSave = true;
|
newOptions.ForceSave = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// var parentId = Id;
|
|
||||||
// if (!video.IsOwnedItem || video.ParentId != parentId)
|
|
||||||
// {
|
|
||||||
// video.IsOwnedItem = true;
|
|
||||||
// video.ParentId = parentId;
|
|
||||||
// newOptions.ForceSave = true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (video == null)
|
if (video == null)
|
||||||
{
|
{
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(true);
|
||||||
@@ -2926,7 +2887,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
.Select(i => i.OfficialRating)
|
.Select(i => i.OfficialRating)
|
||||||
.Where(i => !string.IsNullOrEmpty(i))
|
.Where(i => !string.IsNullOrEmpty(i))
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
.Select(i => new Tuple<string, int?>(i, LocalizationManager.GetRatingLevel(i)))
|
.Select(i => (i, LocalizationManager.GetRatingLevel(i)))
|
||||||
.OrderBy(i => i.Item2 ?? 1000)
|
.OrderBy(i => i.Item2 ?? 1000)
|
||||||
.Select(i => i.Item1);
|
.Select(i => i.Item1);
|
||||||
|
|
||||||
@@ -2973,18 +2934,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
.Where(i => i.ExtraType.HasValue && extraTypes.Contains(i.ExtraType.Value));
|
.Where(i => i.ExtraType.HasValue && extraTypes.Contains(i.ExtraType.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<BaseItem> GetTrailers()
|
|
||||||
{
|
|
||||||
if (this is IHasTrailers)
|
|
||||||
{
|
|
||||||
return ((IHasTrailers)this).LocalTrailerIds.Select(LibraryManager.GetItemById).Where(i => i != null).OrderBy(i => i.SortName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return Array.Empty<BaseItem>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual long GetRunTimeTicksForPlayState()
|
public virtual long GetRunTimeTicksForPlayState()
|
||||||
{
|
{
|
||||||
return RunTimeTicks ?? 0;
|
return RunTimeTicks ?? 0;
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
if (dictionary.ContainsKey(id))
|
if (dictionary.ContainsKey(id))
|
||||||
{
|
{
|
||||||
Logger.LogError(
|
Logger.LogError(
|
||||||
"Found folder containing items with duplicate id. Path: {path}, Child Name: {ChildName}",
|
"Found folder containing items with duplicate id. Path: {Path}, Child Name: {ChildName}",
|
||||||
Path ?? Name,
|
Path ?? Name,
|
||||||
child.Path ?? child.Name);
|
child.Path ?? child.Name);
|
||||||
}
|
}
|
||||||
@@ -425,7 +425,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
{
|
{
|
||||||
if (item.IsFileProtocol)
|
if (item.IsFileProtocol)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Removed item: " + item.Path);
|
Logger.LogDebug("Removed item: {Path}", item.Path);
|
||||||
|
|
||||||
item.SetParent(null);
|
item.SetParent(null);
|
||||||
LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }, this, false);
|
LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }, this, false);
|
||||||
@@ -807,7 +807,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
{
|
{
|
||||||
if (this is not ICollectionFolder)
|
if (this is not ICollectionFolder)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Query requires post-filtering due to LinkedChildren. Type: " + GetType().Name);
|
Logger.LogDebug("{Type}: Query requires post-filtering due to LinkedChildren.", GetType().Name);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1013,6 +1013,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
items = CollapseBoxSetItemsIfNeeded(items, query, this, user, ConfigurationManager, CollectionManager);
|
items = CollapseBoxSetItemsIfNeeded(items, query, this, user, ConfigurationManager, CollectionManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CA1309
|
||||||
if (!string.IsNullOrEmpty(query.NameStartsWithOrGreater))
|
if (!string.IsNullOrEmpty(query.NameStartsWithOrGreater))
|
||||||
{
|
{
|
||||||
items = items.Where(i => string.Compare(query.NameStartsWithOrGreater, i.SortName, StringComparison.InvariantCultureIgnoreCase) < 1);
|
items = items.Where(i => string.Compare(query.NameStartsWithOrGreater, i.SortName, StringComparison.InvariantCultureIgnoreCase) < 1);
|
||||||
@@ -1027,6 +1028,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
{
|
{
|
||||||
items = items.Where(i => string.Compare(query.NameLessThan, i.SortName, StringComparison.InvariantCultureIgnoreCase) == 1);
|
items = items.Where(i => string.Compare(query.NameLessThan, i.SortName, StringComparison.InvariantCultureIgnoreCase) == 1);
|
||||||
}
|
}
|
||||||
|
#pragma warning restore CA1309
|
||||||
|
|
||||||
// This must be the last filter
|
// This must be the last filter
|
||||||
if (!string.IsNullOrEmpty(query.AdjacentTo))
|
if (!string.IsNullOrEmpty(query.AdjacentTo))
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace MediaBrowser.Controller.Entities
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The item has screenshots.
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasScreenshots
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -69,7 +69,7 @@ namespace MediaBrowser.Controller.IO
|
|||||||
if (string.IsNullOrEmpty(newPath))
|
if (string.IsNullOrEmpty(newPath))
|
||||||
{
|
{
|
||||||
// invalid shortcut - could be old or target could just be unavailable
|
// invalid shortcut - could be old or target could just be unavailable
|
||||||
logger.LogWarning("Encountered invalid shortcut: " + fullName);
|
logger.LogWarning("Encountered invalid shortcut: {Path}", fullName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ namespace MediaBrowser.Controller.IO
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, "Error resolving shortcut from {path}", fullName);
|
logger.LogError(ex, "Error resolving shortcut from {Path}", fullName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (flattenFolderDepth > 0 && isDirectory)
|
else if (flattenFolderDepth > 0 && isDirectory)
|
||||||
|
|||||||
@@ -42,11 +42,6 @@ namespace MediaBrowser.Controller
|
|||||||
/// <value>The name of the friendly.</value>
|
/// <value>The name of the friendly.</value>
|
||||||
string FriendlyName { get; }
|
string FriendlyName { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the configured published server url.
|
|
||||||
/// </summary>
|
|
||||||
string PublishedServerUrl { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the system info.
|
/// Gets the system info.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -60,32 +55,29 @@ namespace MediaBrowser.Controller
|
|||||||
/// Gets a URL specific for the request.
|
/// Gets a URL specific for the request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The <see cref="HttpRequest"/> instance.</param>
|
/// <param name="request">The <see cref="HttpRequest"/> instance.</param>
|
||||||
/// <param name="port">Optional port number.</param>
|
|
||||||
/// <returns>An accessible URL.</returns>
|
/// <returns>An accessible URL.</returns>
|
||||||
string GetSmartApiUrl(HttpRequest request, int? port = null);
|
string GetSmartApiUrl(HttpRequest request);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a URL specific for the request.
|
/// Gets a URL specific for the request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="remoteAddr">The remote <see cref="IPAddress"/> of the connection.</param>
|
/// <param name="remoteAddr">The remote <see cref="IPAddress"/> of the connection.</param>
|
||||||
/// <param name="port">Optional port number.</param>
|
|
||||||
/// <returns>An accessible URL.</returns>
|
/// <returns>An accessible URL.</returns>
|
||||||
string GetSmartApiUrl(IPAddress remoteAddr, int? port = null);
|
string GetSmartApiUrl(IPAddress remoteAddr);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a URL specific for the request.
|
/// Gets a URL specific for the request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="hostname">The hostname used in the connection.</param>
|
/// <param name="hostname">The hostname used in the connection.</param>
|
||||||
/// <param name="port">Optional port number.</param>
|
|
||||||
/// <returns>An accessible URL.</returns>
|
/// <returns>An accessible URL.</returns>
|
||||||
string GetSmartApiUrl(string hostname, int? port = null);
|
string GetSmartApiUrl(string hostname);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a localhost URL that can be used to access the API using the loop-back IP address.
|
/// Gets an URL that can be used to access the API over LAN.
|
||||||
/// over HTTP (not HTTPS).
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="allowHttps">A value indicating whether to allow HTTPS.</param>
|
||||||
/// <returns>The API URL.</returns>
|
/// <returns>The API URL.</returns>
|
||||||
string GetLoopbackHttpApiUrl();
|
string GetApiUrlForLocalAccess(bool allowHttps = true);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a local (LAN) URL that can be used to access the API.
|
/// Gets a local (LAN) URL that can be used to access the API.
|
||||||
@@ -103,8 +95,6 @@ namespace MediaBrowser.Controller
|
|||||||
/// <returns>The API URL.</returns>
|
/// <returns>The API URL.</returns>
|
||||||
string GetLocalApiUrl(string hostname, string scheme = null, int? port = null);
|
string GetLocalApiUrl(string hostname, string scheme = null, int? port = null);
|
||||||
|
|
||||||
IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo();
|
|
||||||
|
|
||||||
string ExpandVirtualPath(string path);
|
string ExpandVirtualPath(string path);
|
||||||
|
|
||||||
string ReverseVirtualPath(string path);
|
string ReverseVirtualPath(string path);
|
||||||
|
|||||||
@@ -30,13 +30,6 @@ namespace MediaBrowser.Controller.Library
|
|||||||
/// <returns>IEnumerable<MediaStream>.</returns>
|
/// <returns>IEnumerable<MediaStream>.</returns>
|
||||||
List<MediaStream> GetMediaStreams(Guid itemId);
|
List<MediaStream> GetMediaStreams(Guid itemId);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the media streams.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mediaSourceId">The media source identifier.</param>
|
|
||||||
/// <returns>IEnumerable<MediaStream>.</returns>
|
|
||||||
List<MediaStream> GetMediaStreams(string mediaSourceId);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the media streams.
|
/// Gets the media streams.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -15,10 +15,10 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Diacritics" Version="3.3.4" />
|
<PackageReference Include="Diacritics" Version="3.3.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0-rc.2*" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0" PrivateAssets="All" />
|
||||||
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="6.0.0-rc.2*" />
|
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user