mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-01-15 23:58:57 +00:00
Compare commits
181 Commits
v10.8.0-al
...
v10.8.0-al
| 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:
|
||||
packageType: sdk
|
||||
version: ${{ parameters.DotNetSdkVersion }}
|
||||
includePreviewVersions: true
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Install ABI CompatibilityChecker Tool'
|
||||
|
||||
@@ -54,7 +54,6 @@ jobs:
|
||||
inputs:
|
||||
packageType: sdk
|
||||
version: ${{ parameters.DotNetSdkVersion }}
|
||||
includePreviewVersions: true
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Publish Server'
|
||||
|
||||
@@ -181,7 +181,7 @@ jobs:
|
||||
inputs:
|
||||
sshEndpoint: repository
|
||||
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
|
||||
displayName: 'Publish NuGet packages'
|
||||
@@ -199,7 +199,6 @@ jobs:
|
||||
inputs:
|
||||
packageType: 'sdk'
|
||||
version: '6.0.x'
|
||||
includePreviewVersions: true
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Build Stable Nuget packages'
|
||||
|
||||
@@ -41,7 +41,6 @@ jobs:
|
||||
inputs:
|
||||
packageType: sdk
|
||||
version: ${{ parameters.DotNetSdkVersion }}
|
||||
includePreviewVersions: true
|
||||
|
||||
- task: SonarCloudPrepare@1
|
||||
displayName: 'Prepare analysis on SonarCloud'
|
||||
|
||||
51
.github/ISSUE_TEMPLATE/bug_report.md
vendored
51
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -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. -->
|
||||
106
.github/ISSUE_TEMPLATE/issue report.yml
vendored
Normal file
106
.github/ISSUE_TEMPLATE/issue report.yml
vendored
Normal file
@@ -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
|
||||
3
.github/workflows/codeql-analysis.yml
vendored
3
.github/workflows/codeql-analysis.yml
vendored
@@ -25,8 +25,7 @@ jobs:
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
include-prerelease: true
|
||||
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
|
||||
124
.github/workflows/openapi.yml
vendored
Normal file
124
.github/workflows/openapi.yml
vendored
Normal file
@@ -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>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
</PropertyGroup>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
||||
namespace Emby.Dlna.ContentDirectory
|
||||
@@ -13,24 +11,29 @@ namespace Emby.Dlna.ContentDirectory
|
||||
/// Initializes a new instance of the <see cref="ServerItem"/> class.
|
||||
/// </summary>
|
||||
/// <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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the underlying base item.
|
||||
/// Gets the underlying base item.
|
||||
/// </summary>
|
||||
public BaseItem Item { get; set; }
|
||||
public BaseItem Item { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the DLNA item type.
|
||||
/// Gets the DLNA item type.
|
||||
/// </summary>
|
||||
public StubType? StubType { get; set; }
|
||||
public StubType? StubType { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -729,7 +729,7 @@ namespace Emby.Dlna.Didl
|
||||
{
|
||||
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)
|
||||
{
|
||||
LogUnmatchedProfile(deviceInfo);
|
||||
_logger.LogInformation("No matching device profile found. The default will need to be used. \n{@Profile}", deviceInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -122,23 +122,6 @@ namespace Emby.Dlna
|
||||
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>
|
||||
/// Attempts to match a device with a profile.
|
||||
/// Rules:
|
||||
@@ -416,7 +399,7 @@ namespace Emby.Dlna
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateProfile(DeviceProfile profile)
|
||||
public void UpdateProfile(string profileId, DeviceProfile profile)
|
||||
{
|
||||
profile = ReserializeProfile(profile);
|
||||
|
||||
@@ -430,7 +413,7 @@ namespace Emby.Dlna
|
||||
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 path = Path.Combine(UserProfilesPath, newFilename);
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -52,7 +52,6 @@ namespace Emby.Dlna.Main
|
||||
private readonly ISocketFactory _socketFactory;
|
||||
private readonly INetworkManager _networkManager;
|
||||
private readonly object _syncLock = new object();
|
||||
private readonly NetworkConfiguration _netConfig;
|
||||
private readonly bool _disabled;
|
||||
|
||||
private PlayToManager _manager;
|
||||
@@ -125,8 +124,8 @@ namespace Emby.Dlna.Main
|
||||
config);
|
||||
Current = this;
|
||||
|
||||
_netConfig = config.GetConfiguration<NetworkConfiguration>("network");
|
||||
_disabled = appHost.ListenWithHttps && _netConfig.RequireHttps;
|
||||
var netConfig = config.GetConfiguration<NetworkConfiguration>("network");
|
||||
_disabled = appHost.ListenWithHttps && netConfig.RequireHttps;
|
||||
|
||||
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)
|
||||
{
|
||||
try
|
||||
@@ -273,7 +267,7 @@ namespace Emby.Dlna.Main
|
||||
Environment.OSVersion.VersionString,
|
||||
_config.GetDlnaConfiguration().SendOnlyMatchedHost)
|
||||
{
|
||||
LogFunction = LogMessage,
|
||||
LogFunction = (msg) => _logger.LogDebug("{Msg}", msg),
|
||||
SupportPnpRootDevice = false
|
||||
};
|
||||
|
||||
@@ -318,15 +312,9 @@ namespace Emby.Dlna.Main
|
||||
|
||||
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);
|
||||
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 uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(false) + descriptorUri);
|
||||
|
||||
var device = new SsdpRootDevice
|
||||
{
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Emby.Dlna.Service
|
||||
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
|
||||
{
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Emby.Drawing
|
||||
public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
{
|
||||
// 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
|
||||
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CA1819
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -253,6 +255,8 @@ namespace Emby.Naming.Common
|
||||
},
|
||||
// <!-- foo.ep01, foo.EP_01 -->
|
||||
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)
|
||||
{
|
||||
DateTimeFormats = new[]
|
||||
@@ -371,6 +375,20 @@ namespace Emby.Naming.Common
|
||||
IsOptimistic = 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[]
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
|
||||
29
Emby.Naming/TV/SeriesInfo.cs
Normal file
29
Emby.Naming/TV/SeriesInfo.cs
Normal file
@@ -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; }
|
||||
}
|
||||
}
|
||||
61
Emby.Naming/TV/SeriesPathParser.cs
Normal file
61
Emby.Naming/TV/SeriesPathParser.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Emby.Naming/TV/SeriesPathParserResult.cs
Normal file
19
Emby.Naming/TV/SeriesPathParserResult.cs
Normal file
@@ -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; }
|
||||
}
|
||||
}
|
||||
49
Emby.Naming/TV/SeriesResolver.cs
Normal file
49
Emby.Naming/TV/SeriesResolver.cs
Normal file
@@ -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
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
@@ -56,6 +57,7 @@ using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Chapters;
|
||||
using MediaBrowser.Controller.ClientEvent;
|
||||
using MediaBrowser.Controller.Collections;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
@@ -117,7 +119,7 @@ namespace Emby.Server.Implementations
|
||||
/// <summary>
|
||||
/// The disposable parts.
|
||||
/// </summary>
|
||||
private readonly List<IDisposable> _disposableParts = new List<IDisposable>();
|
||||
private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new ();
|
||||
|
||||
private readonly IFileSystem _fileSystemManager;
|
||||
private readonly IConfiguration _startupConfig;
|
||||
@@ -128,7 +130,6 @@ namespace Emby.Server.Implementations
|
||||
private List<Type> _creatingInstances;
|
||||
private IMediaEncoder _mediaEncoder;
|
||||
private ISessionManager _sessionManager;
|
||||
private string[] _urlPrefixes;
|
||||
|
||||
/// <summary>
|
||||
/// 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="options">Instance of the <see cref="IStartupOptions"/> 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(
|
||||
IServerApplicationPaths applicationPaths,
|
||||
ILoggerFactory loggerFactory,
|
||||
IStartupOptions options,
|
||||
IConfiguration startupConfig,
|
||||
IFileSystem fileSystem,
|
||||
IServiceCollection serviceCollection)
|
||||
IConfiguration startupConfig)
|
||||
{
|
||||
ApplicationPaths = applicationPaths;
|
||||
LoggerFactory = loggerFactory;
|
||||
_startupOptions = options;
|
||||
_startupConfig = startupConfig;
|
||||
_fileSystemManager = fileSystem;
|
||||
ServiceCollection = serviceCollection;
|
||||
_fileSystemManager = new ManagedFileSystem(LoggerFactory.CreateLogger<ManagedFileSystem>(), applicationPaths);
|
||||
|
||||
Logger = LoggerFactory.CreateLogger<ApplicationHost>();
|
||||
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
|
||||
_fileSystemManager.AddShortcutHandler(new MbLinkShortcutHandler(_fileSystemManager));
|
||||
|
||||
ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
|
||||
ApplicationVersionString = ApplicationVersion.ToString(3);
|
||||
@@ -214,7 +210,7 @@ namespace Emby.Server.Implementations
|
||||
/// <summary>
|
||||
/// Gets the <see cref="INetworkManager"/> singleton instance.
|
||||
/// </summary>
|
||||
public INetworkManager NetManager { get; internal set; }
|
||||
public INetworkManager NetManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
protected ILogger<ApplicationHost> Logger { get; }
|
||||
|
||||
protected IServiceCollection ServiceCollection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger factory.
|
||||
/// </summary>
|
||||
protected ILoggerFactory LoggerFactory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the application paths.
|
||||
/// Gets the application paths.
|
||||
/// </summary>
|
||||
/// <value>The application paths.</value>
|
||||
protected IServerApplicationPaths ApplicationPaths { get; set; }
|
||||
protected IServerApplicationPaths ApplicationPaths { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the configuration manager.
|
||||
/// Gets the configuration manager.
|
||||
/// </summary>
|
||||
/// <value>The configuration manager.</value>
|
||||
public ServerConfigurationManager ConfigurationManager { get; set; }
|
||||
public ServerConfigurationManager ConfigurationManager { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the service provider.
|
||||
@@ -350,22 +344,6 @@ namespace Emby.Server.Implementations
|
||||
.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>
|
||||
/// Creates the instance safe.
|
||||
/// </summary>
|
||||
@@ -375,7 +353,7 @@ namespace Emby.Server.Implementations
|
||||
{
|
||||
_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);
|
||||
foreach (var entry in _creatingInstances)
|
||||
@@ -385,7 +363,7 @@ namespace Emby.Server.Implementations
|
||||
|
||||
_pluginManager.FailPlugin(type.Assembly);
|
||||
|
||||
throw new ExternalException("DI Loop detected.");
|
||||
throw new TypeLoadException("DI Loop detected");
|
||||
}
|
||||
|
||||
try
|
||||
@@ -418,8 +396,15 @@ namespace Emby.Server.Implementations
|
||||
public IEnumerable<Type> GetExportTypes<T>()
|
||||
{
|
||||
var currentType = typeof(T);
|
||||
|
||||
return _allConcreteTypes.Where(i => currentType.IsAssignableFrom(i));
|
||||
var numberOfConcreteTypes = _allConcreteTypes.Length;
|
||||
for (var i = 0; i < numberOfConcreteTypes; i++)
|
||||
{
|
||||
var type = _allConcreteTypes[i];
|
||||
if (currentType.IsAssignableFrom(type))
|
||||
{
|
||||
yield return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -434,9 +419,9 @@ namespace Emby.Server.Implementations
|
||||
|
||||
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)
|
||||
{
|
||||
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/>
|
||||
public void Init()
|
||||
public void Init(IServiceCollection serviceCollection)
|
||||
{
|
||||
DiscoverTypes();
|
||||
|
||||
@@ -551,128 +536,129 @@ namespace Emby.Server.Implementations
|
||||
CertificatePath = networkConfiguration.CertificatePath;
|
||||
Certificate = GetCertificate(CertificatePath, networkConfiguration.CertificatePassword);
|
||||
|
||||
RegisterServices();
|
||||
RegisterServices(serviceCollection);
|
||||
|
||||
_pluginManager.RegisterServices(ServiceCollection);
|
||||
_pluginManager.RegisterServices(serviceCollection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers services/resources with the service collection that will be available via DI.
|
||||
/// </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<IConfigurationManager>(ConfigurationManager);
|
||||
ServiceCollection.AddSingleton<IApplicationHost>(this);
|
||||
ServiceCollection.AddSingleton<IPluginManager>(_pluginManager);
|
||||
ServiceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
|
||||
serviceCollection.AddSingleton<IServerConfigurationManager>(ConfigurationManager);
|
||||
serviceCollection.AddSingleton<IConfigurationManager>(ConfigurationManager);
|
||||
serviceCollection.AddSingleton<IApplicationHost>(this);
|
||||
serviceCollection.AddSingleton(_pluginManager);
|
||||
serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
|
||||
|
||||
ServiceCollection.AddSingleton(_fileSystemManager);
|
||||
ServiceCollection.AddSingleton<TmdbClientManager>();
|
||||
serviceCollection.AddSingleton(_fileSystemManager);
|
||||
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<IServerApplicationPaths>(ApplicationPaths);
|
||||
serviceCollection.AddSingleton<IServerApplicationHost>(this);
|
||||
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<IUserDataManager, UserDataManager>();
|
||||
serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
|
||||
serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
|
||||
|
||||
ServiceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
|
||||
serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
|
||||
|
||||
ServiceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
|
||||
ServiceCollection.AddSingleton<EncodingHelper>();
|
||||
serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
|
||||
serviceCollection.AddSingleton<EncodingHelper>();
|
||||
|
||||
// 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<IProviderManager>(provider.GetRequiredService<IProviderManager>));
|
||||
ServiceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
|
||||
ServiceCollection.AddSingleton<ILibraryManager, LibraryManager>();
|
||||
serviceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
|
||||
serviceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>));
|
||||
serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
|
||||
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
|
||||
ServiceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>));
|
||||
ServiceCollection.AddSingleton<IDtoService, DtoService>();
|
||||
serviceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>));
|
||||
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<ILiveTvManager, LiveTvManager>();
|
||||
serviceCollection.AddSingleton<LiveTvDtoService>();
|
||||
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<IQuickConnect, QuickConnectManager>();
|
||||
serviceCollection.AddSingleton<IAuthService, AuthService>();
|
||||
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.AddScoped<MediaInfoHelper>();
|
||||
ServiceCollection.AddScoped<AudioHelper>();
|
||||
ServiceCollection.AddScoped<DynamicHlsHelper>();
|
||||
|
||||
ServiceCollection.AddSingleton<IDirectoryService, DirectoryService>();
|
||||
serviceCollection.AddSingleton<TranscodingJobHelper>();
|
||||
serviceCollection.AddScoped<MediaInfoHelper>();
|
||||
serviceCollection.AddScoped<AudioHelper>();
|
||||
serviceCollection.AddScoped<DynamicHlsHelper>();
|
||||
serviceCollection.AddScoped<IClientEventLogger, ClientEventLogger>();
|
||||
serviceCollection.AddSingleton<IDirectoryService, DirectoryService>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -795,8 +781,6 @@ namespace Emby.Server.Implementations
|
||||
|
||||
_pluginManager.CreatePlugins();
|
||||
|
||||
_urlPrefixes = GetUrlPrefixes().ToArray();
|
||||
|
||||
Resolve<ILibraryManager>().AddParts(
|
||||
GetExports<IResolverIgnoreRule>(),
|
||||
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>
|
||||
/// Called when [configuration updated].
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender.</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 networkConfiguration = ConfigurationManager.GetNetworkConfiguration();
|
||||
@@ -898,8 +862,8 @@ namespace Emby.Server.Implementations
|
||||
if (HttpPort != 0 && HttpsPort != 0)
|
||||
{
|
||||
// Need to restart if ports have changed
|
||||
if (networkConfiguration.HttpServerPortNumber != HttpPort ||
|
||||
networkConfiguration.HttpsPortNumber != HttpsPort)
|
||||
if (networkConfiguration.HttpServerPortNumber != HttpPort
|
||||
|| networkConfiguration.HttpsPortNumber != HttpsPort)
|
||||
{
|
||||
if (ConfigurationManager.Configuration.IsPortAuthorized)
|
||||
{
|
||||
@@ -911,11 +875,6 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
if (!_urlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
requiresRestart = true;
|
||||
}
|
||||
|
||||
if (ValidateSslCertificate(networkConfiguration))
|
||||
{
|
||||
requiresRestart = true;
|
||||
@@ -957,7 +916,7 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
|
||||
/// <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>
|
||||
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)
|
||||
{
|
||||
return new PublicSystemInfo
|
||||
@@ -1118,7 +1072,7 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetSmartApiUrl(IPAddress remoteAddr, int? port = null)
|
||||
public string GetSmartApiUrl(IPAddress remoteAddr)
|
||||
{
|
||||
// Published server ends with a /
|
||||
if (!string.IsNullOrEmpty(PublishedServerUrl))
|
||||
@@ -1127,18 +1081,12 @@ namespace Emby.Server.Implementations
|
||||
return PublishedServerUrl.Trim('/');
|
||||
}
|
||||
|
||||
string smart = NetManager.GetBindInterface(remoteAddr, out 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('/');
|
||||
}
|
||||
|
||||
string smart = NetManager.GetBindInterface(remoteAddr, out var port);
|
||||
return GetLocalApiUrl(smart.Trim('/'), null, port);
|
||||
}
|
||||
|
||||
/// <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
|
||||
if (ConfigurationManager.GetNetworkConfiguration().EnablePublishedServerUriByRequest)
|
||||
@@ -1159,18 +1107,12 @@ namespace Emby.Server.Implementations
|
||||
return PublishedServerUrl.Trim('/');
|
||||
}
|
||||
|
||||
string smart = NetManager.GetBindInterface(request, out 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('/');
|
||||
}
|
||||
|
||||
string smart = NetManager.GetBindInterface(request, out var port);
|
||||
return GetLocalApiUrl(smart.Trim('/'), request.Scheme, port);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetSmartApiUrl(string hostname, int? port = null)
|
||||
public string GetSmartApiUrl(string hostname)
|
||||
{
|
||||
// Published server ends with a /
|
||||
if (!string.IsNullOrEmpty(PublishedServerUrl))
|
||||
@@ -1179,31 +1121,29 @@ namespace Emby.Server.Implementations
|
||||
return PublishedServerUrl.Trim('/');
|
||||
}
|
||||
|
||||
string smart = NetManager.GetBindInterface(hostname, out 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('/');
|
||||
}
|
||||
|
||||
string smart = NetManager.GetBindInterface(hostname, out var port);
|
||||
return GetLocalApiUrl(smart.Trim('/'), null, port);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLoopbackHttpApiUrl()
|
||||
public string GetApiUrlForLocalAccess(bool allowHttps)
|
||||
{
|
||||
if (NetManager.IsIP6Enabled)
|
||||
{
|
||||
return GetLocalApiUrl("::1", Uri.UriSchemeHttp, HttpPort);
|
||||
}
|
||||
|
||||
return GetLocalApiUrl("127.0.0.1", Uri.UriSchemeHttp, HttpPort);
|
||||
// With an empty source, the port will be null
|
||||
string smart = NetManager.GetBindInterface(string.Empty, out _);
|
||||
var scheme = allowHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
|
||||
var port = allowHttps ? HttpsPort : HttpPort;
|
||||
return GetLocalApiUrl(smart.Trim('/'), scheme, port);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
// not. For consistency, always trim the trailing slash.
|
||||
return new UriBuilder
|
||||
@@ -1277,12 +1217,15 @@ namespace Emby.Server.Implementations
|
||||
|
||||
Logger.LogInformation("Disposing {Type}", type.Name);
|
||||
|
||||
var parts = _disposableParts.Distinct().Where(i => i.GetType() != type).ToList();
|
||||
_disposableParts.Clear();
|
||||
|
||||
foreach (var part in parts)
|
||||
foreach (var (part, _) in _disposableParts)
|
||||
{
|
||||
Logger.LogInformation("Disposing {Type}", part.GetType().Name);
|
||||
var partType = part.GetType();
|
||||
if (partType == type)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.LogInformation("Disposing {Type}", partType.Name);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -1290,9 +1233,11 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error disposing {Type}", part.GetType().Name);
|
||||
Logger.LogError(ex, "Error disposing {Type}", partType.Name);
|
||||
}
|
||||
}
|
||||
|
||||
_disposableParts.Clear();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
||||
@@ -134,14 +134,11 @@ namespace Emby.Server.Implementations.Dto
|
||||
var dto = GetBaseItemDtoInternal(item, options, user, owner);
|
||||
if (item is LiveTvChannel tvChannel)
|
||||
{
|
||||
var list = new List<(BaseItemDto, LiveTvChannel)>(1) { (dto, tvChannel) };
|
||||
LivetvManager.AddChannelInfo(list, options, user);
|
||||
LivetvManager.AddChannelInfo(new[] { (dto, tvChannel) }, options, user);
|
||||
}
|
||||
else if (item is LiveTvProgram)
|
||||
{
|
||||
var list = new List<(BaseItem, BaseItemDto)>(1) { (item, dto) };
|
||||
var task = LivetvManager.AddInfoToProgramDto(list, options.Fields, user);
|
||||
Task.WaitAll(task);
|
||||
LivetvManager.AddInfoToProgramDto(new[] { (item, dto) }, options.Fields, user).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
if (item is IItemByName itemByName
|
||||
@@ -497,7 +494,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -755,15 +752,6 @@ namespace Emby.Server.Implementations.Dto
|
||||
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))
|
||||
{
|
||||
dto.Genres = item.Genres;
|
||||
|
||||
@@ -25,12 +25,12 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiscUtils.Udf" Version="0.16.13" />
|
||||
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Mono.Nat" Version="3.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" />
|
||||
<PackageReference Include="Mono.Nat" Version="3.0.2" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
|
||||
<PackageReference Include="sharpcompress" Version="0.30.0" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
|
||||
if (!auth.HasToken)
|
||||
{
|
||||
throw new AuthenticationException("Request does not contain a token.");
|
||||
return auth;
|
||||
}
|
||||
|
||||
if (!auth.IsAuthenticated)
|
||||
|
||||
@@ -35,7 +35,12 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// <inheritdoc />
|
||||
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
|
||||
{
|
||||
_logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress);
|
||||
|
||||
@@ -267,7 +267,7 @@ namespace Emby.Server.Implementations.IO
|
||||
if (_fileSystemWatchers.TryAdd(path, newWatcher))
|
||||
{
|
||||
newWatcher.EnableRaisingEvents = true;
|
||||
_logger.LogInformation("Watching directory " + path);
|
||||
_logger.LogInformation("Watching directory {Path}", path);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -276,7 +276,7 @@ namespace Emby.Server.Implementations.IO
|
||||
}
|
||||
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.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -23,6 +21,11 @@ namespace Emby.Server.Implementations.IO
|
||||
private readonly string _tempPath;
|
||||
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(
|
||||
ILogger<ManagedFileSystem> logger,
|
||||
IApplicationPaths applicationPaths)
|
||||
@@ -31,6 +34,7 @@ namespace Emby.Server.Implementations.IO
|
||||
_tempPath = applicationPaths.TempDirectory;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void AddShortcutHandler(IShortcutHandler handler)
|
||||
{
|
||||
_shortcutHandlers.Add(handler);
|
||||
@@ -72,6 +76,7 @@ namespace Emby.Server.Implementations.IO
|
||||
return handler?.Resolve(filename);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string MakeAbsolutePath(string folderPath, string filePath)
|
||||
{
|
||||
// path is actually a stream
|
||||
@@ -358,11 +363,13 @@ namespace Emby.Server.Implementations.IO
|
||||
return GetCreationTimeUtc(GetFileSystemInfo(path));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual DateTime GetCreationTimeUtc(FileSystemMetadata info)
|
||||
{
|
||||
return info.CreationTimeUtc;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual DateTime GetLastWriteTimeUtc(FileSystemMetadata info)
|
||||
{
|
||||
return info.LastWriteTimeUtc;
|
||||
@@ -397,6 +404,7 @@ namespace Emby.Server.Implementations.IO
|
||||
return GetLastWriteTimeUtc(GetFileSystemInfo(path));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void SetHidden(string path, bool isHidden)
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
@@ -421,6 +429,7 @@ namespace Emby.Server.Implementations.IO
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void SetAttributes(string path, bool isHidden, bool readOnly)
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
@@ -444,7 +453,7 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
if (readOnly)
|
||||
{
|
||||
attributes = attributes | FileAttributes.ReadOnly;
|
||||
attributes |= FileAttributes.ReadOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -453,7 +462,7 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
if (isHidden)
|
||||
{
|
||||
attributes = attributes | FileAttributes.Hidden;
|
||||
attributes |= FileAttributes.Hidden;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -498,6 +507,7 @@ namespace Emby.Server.Implementations.IO
|
||||
File.Copy(temp1, file2, true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool ContainsSubPath(string parentPath, string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(parentPath))
|
||||
@@ -515,6 +525,7 @@ namespace Emby.Server.Implementations.IO
|
||||
_isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string NormalizePath(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
@@ -530,6 +541,7 @@ namespace Emby.Server.Implementations.IO
|
||||
return Path.TrimEndingDirectorySeparator(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool AreEqual(string path1, string path2)
|
||||
{
|
||||
if (path1 == null && path2 == null)
|
||||
@@ -548,6 +560,7 @@ namespace Emby.Server.Implementations.IO
|
||||
_isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string GetFileNameWithoutExtension(FileSystemMetadata info)
|
||||
{
|
||||
if (info.IsDirectory)
|
||||
@@ -558,11 +571,11 @@ namespace Emby.Server.Implementations.IO
|
||||
return Path.GetFileNameWithoutExtension(info.FullName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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.IndexOf("://", StringComparison.OrdinalIgnoreCase) != -1 &&
|
||||
!path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
|
||||
if (path.Contains("://", StringComparison.OrdinalIgnoreCase)
|
||||
&& !path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -570,17 +583,23 @@ namespace Emby.Server.Implementations.IO
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void DeleteFile(string path)
|
||||
{
|
||||
SetAttributes(path, false, false);
|
||||
File.Delete(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual List<FileSystemMetadata> GetDrives()
|
||||
{
|
||||
// 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
|
||||
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
|
||||
{
|
||||
Name = d.Name,
|
||||
@@ -589,16 +608,19 @@ namespace Emby.Server.Implementations.IO
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
|
||||
{
|
||||
return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", GetEnumerationOptions(recursive)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false)
|
||||
{
|
||||
return GetFiles(path, null, false, recursive);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
|
||||
{
|
||||
var enumerationOptions = GetEnumerationOptions(recursive);
|
||||
@@ -629,6 +651,7 @@ namespace Emby.Server.Implementations.IO
|
||||
return ToMetadata(files);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
|
||||
{
|
||||
var directoryInfo = new DirectoryInfo(path);
|
||||
@@ -642,16 +665,19 @@ namespace Emby.Server.Implementations.IO
|
||||
return infos.Select(GetFileSystemMetadata);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
|
||||
{
|
||||
return Directory.EnumerateDirectories(path, "*", GetEnumerationOptions(recursive));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<string> GetFilePaths(string path, bool recursive = false)
|
||||
{
|
||||
return GetFilePaths(path, null, false, recursive);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<string> GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
|
||||
{
|
||||
var enumerationOptions = GetEnumerationOptions(recursive);
|
||||
@@ -682,6 +708,7 @@ namespace Emby.Server.Implementations.IO
|
||||
return files;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
|
||||
{
|
||||
return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive));
|
||||
|
||||
@@ -333,8 +333,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
try
|
||||
{
|
||||
var task = BaseItem.ChannelManager.DeleteItem(item);
|
||||
Task.WaitAll(task);
|
||||
BaseItem.ChannelManager.DeleteItem(item).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
@@ -492,7 +491,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -799,7 +798,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
var userRootPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||
|
||||
_logger.LogDebug("Creating userRootPath at {path}", userRootPath);
|
||||
_logger.LogDebug("Creating userRootPath at {Path}", userRootPath);
|
||||
Directory.CreateDirectory(userRootPath);
|
||||
|
||||
var newItemId = GetNewItemId(userRootPath, typeof(UserRootFolder));
|
||||
@@ -810,7 +809,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error creating UserRootFolder {path}", newItemId);
|
||||
_logger.LogError(ex, "Error creating UserRootFolder {Path}", newItemId);
|
||||
}
|
||||
|
||||
if (tmpItem == null)
|
||||
@@ -827,7 +826,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
_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)
|
||||
{
|
||||
_logger.LogError(ex, "Error resolving shortcut file {file}", i);
|
||||
_logger.LogError(ex, "Error resolving shortcut file {File}", i);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
@@ -1698,7 +1697,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (video == null)
|
||||
{
|
||||
_logger.LogError("Intro resolver returned null for {path}.", info.Path);
|
||||
_logger.LogError("Intro resolver returned null for {Path}.", info.Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1717,7 +1716,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error resolving path {path}.", info.Path);
|
||||
_logger.LogError(ex, "Error resolving path {Path}.", info.Path);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace Emby.Server.Implementations.Library
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly ILocalizationManager _localizationManager;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
|
||||
private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
|
||||
@@ -61,7 +62,8 @@ namespace Emby.Server.Implementations.Library
|
||||
ILogger<MediaSourceManager> logger,
|
||||
IFileSystem fileSystem,
|
||||
IUserDataManager userDataManager,
|
||||
IMediaEncoder mediaEncoder)
|
||||
IMediaEncoder mediaEncoder,
|
||||
IDirectoryService directoryService)
|
||||
{
|
||||
_itemRepo = itemRepo;
|
||||
_userManager = userManager;
|
||||
@@ -72,6 +74,7 @@ namespace Emby.Server.Implementations.Library
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_localizationManager = localizationManager;
|
||||
_appPaths = applicationPaths;
|
||||
_directoryService = directoryService;
|
||||
}
|
||||
|
||||
public void AddParts(IEnumerable<IMediaSourceProvider> providers)
|
||||
@@ -106,16 +109,6 @@ namespace Emby.Server.Implementations.Library
|
||||
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)
|
||||
{
|
||||
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))
|
||||
{
|
||||
await item.RefreshMetadata(
|
||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
new MetadataRefreshOptions(_directoryService)
|
||||
{
|
||||
EnableRemoteContentProbe = true,
|
||||
MetadataRefreshMode = MetadataRefreshMode.FullRefresh
|
||||
@@ -212,6 +205,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return SortMediaSources(list);
|
||||
}
|
||||
|
||||
/// <inheritdoc />>
|
||||
public MediaProtocol GetPathProtocol(string path)
|
||||
{
|
||||
if (path.StartsWith("Rtsp", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -258,7 +252,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
if (path != null)
|
||||
{
|
||||
if (path.IndexOf(".m3u", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (path.Contains(".m3u", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -297,7 +291,7 @@ namespace Emby.Server.Implementations.Library
|
||||
catch (Exception ex)
|
||||
{
|
||||
_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();
|
||||
}
|
||||
|
||||
// TODO: Don't hardcode this
|
||||
const bool isAudio = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (mediaSource.MediaStreams.Any(i => i.Index != -1) || !mediaSource.SupportsProbing)
|
||||
{
|
||||
AddMediaInfo(mediaSource, isAudio);
|
||||
AddMediaInfo(mediaSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -509,14 +500,14 @@ namespace Emby.Server.Implementations.Library
|
||||
string cacheKey = request.OpenToken;
|
||||
|
||||
await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)
|
||||
.AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken)
|
||||
.AddMediaInfoWithProbe(mediaSource, false, cacheKey, true, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error probing live tv stream");
|
||||
AddMediaInfo(mediaSource, isAudio);
|
||||
AddMediaInfo(mediaSource);
|
||||
}
|
||||
|
||||
// TODO: @bond Fix
|
||||
@@ -536,7 +527,7 @@ namespace Emby.Server.Implementations.Library
|
||||
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;
|
||||
|
||||
@@ -855,9 +846,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return (provider, keyId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
|
||||
@@ -151,7 +151,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
{
|
||||
if (parser.IsMultiPart(path))
|
||||
{
|
||||
logger.LogDebug("Found multi-disc folder: " + path);
|
||||
logger.LogDebug("Found multi-disc folder: {Path}", path);
|
||||
Interlocked.Increment(ref discSubfolderCount);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -54,6 +54,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
return null;
|
||||
}
|
||||
|
||||
var seriesInfo = Naming.TV.SeriesResolver.Resolve(_libraryManager.GetNamingOptions(), args.Path);
|
||||
|
||||
var collectionType = args.GetCollectionType();
|
||||
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -63,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
return new Series
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
Path = args.Path,
|
||||
Name = Path.GetFileName(args.Path)
|
||||
Name = seriesInfo.Name
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error validating IBN entry {person}", person);
|
||||
_logger.LogError(ex, "Error validating IBN entry {Person}", person);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
_logger.LogInformation("Streaming Channel " + channelId);
|
||||
_logger.LogInformation("Streaming Channel {Id}", channelId);
|
||||
|
||||
var result = string.IsNullOrEmpty(streamId) ?
|
||||
null :
|
||||
@@ -1027,7 +1027,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
var stream = new MediaSourceInfo
|
||||
{
|
||||
EncoderPath = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
|
||||
EncoderPath = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
|
||||
EncoderProtocol = MediaProtocol.Http,
|
||||
Path = info.Path,
|
||||
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);
|
||||
|
||||
recordingStatus = RecordingStatus.Completed;
|
||||
_logger.LogInformation("Recording completed: {recordPath}", recordPath);
|
||||
_logger.LogInformation("Recording completed: {RecordPath}", recordPath);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogInformation("Recording stopped: {recordPath}", recordPath);
|
||||
_logger.LogInformation("Recording stopped: {RecordPath}", recordPath);
|
||||
recordingStatus = RecordingStatus.Completed;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error recording to {recordPath}", recordPath);
|
||||
_logger.LogError(ex, "Error recording to {RecordPath}", recordPath);
|
||||
recordingStatus = RecordingStatus.Error;
|
||||
}
|
||||
|
||||
@@ -1404,7 +1404,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
}
|
||||
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
|
||||
};
|
||||
|
||||
var commandLineLogMessage = processStartInfo.FileName + " " + processStartInfo.Arguments;
|
||||
_logger.LogInformation(commandLineLogMessage);
|
||||
_logger.LogInformation("{Filename} {Arguments}", processStartInfo.FileName, processStartInfo.Arguments);
|
||||
|
||||
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
|
||||
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);
|
||||
|
||||
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
|
||||
{
|
||||
@@ -225,13 +224,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Stopping ffmpeg recording process for {path}", _targetPath);
|
||||
_logger.LogInformation("Stopping ffmpeg recording process for {Path}", _targetPath);
|
||||
|
||||
_process.StandardInput.WriteLine("q");
|
||||
}
|
||||
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)
|
||||
@@ -241,7 +240,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Calling recording process.WaitForExit for {path}", _targetPath);
|
||||
_logger.LogInformation("Calling recording process.WaitForExit for {Path}", _targetPath);
|
||||
|
||||
if (_process.WaitForExit(10000))
|
||||
{
|
||||
@@ -250,7 +249,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
}
|
||||
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)
|
||||
@@ -260,13 +259,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Killing ffmpeg recording process for {path}", _targetPath);
|
||||
_logger.LogInformation("Killing ffmpeg recording process for {Path}", _targetPath);
|
||||
|
||||
_process.Kill();
|
||||
}
|
||||
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)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting image info for {name}", info.Name);
|
||||
_logger.LogError(ex, "Error getting image info for {Name}", info.Name);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -1054,7 +1054,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_logger.LogDebug("Refreshing guide from {name}", service.Name);
|
||||
_logger.LogDebug("Refreshing guide from {Name}", service.Name);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -1135,7 +1135,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
}
|
||||
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++;
|
||||
@@ -1248,7 +1248,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
}
|
||||
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++;
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
// Dummy this up so that direct play checks can still run
|
||||
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));
|
||||
|
||||
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);
|
||||
IPAddress localAddress = null;
|
||||
@@ -147,7 +147,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
// OpenedMediaSource.Path = tempFile;
|
||||
// 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;
|
||||
// OpenedMediaSource.SupportsDirectPlay = false;
|
||||
// OpenedMediaSource.SupportsDirectStream = true;
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
// OpenedMediaSource.Path = tempFile;
|
||||
// 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;
|
||||
|
||||
// OpenedMediaSource.Path = TempFilePath;
|
||||
|
||||
1
Emby.Server.Implementations/Localization/Core/be.json
Normal file
1
Emby.Server.Implementations/Localization/Core/be.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"NotificationOptionInstallationFailed": "Instalada fiasko",
|
||||
"NotificationOptionInstallationFailed": "Instalada malsukceso",
|
||||
"NotificationOptionAudioPlaybackStopped": "Ludado de sono haltis",
|
||||
"NotificationOptionAudioPlayback": "Ludado de sono lanĉis",
|
||||
"NameSeasonUnknown": "Sezono Nekonata",
|
||||
@@ -48,17 +48,17 @@
|
||||
"Shows": "Serioj",
|
||||
"HeaderFavoriteShows": "Favorataj Serioj",
|
||||
"TvShows": "TV-serioj",
|
||||
"Favorites": "Favoratoj",
|
||||
"Favorites": "Favorataj",
|
||||
"TaskCleanLogs": "Purigi Ĵurnalan Katalogon",
|
||||
"TaskRefreshLibrary": "Skanu Plurmeditekon",
|
||||
"TaskRefreshLibrary": "Skani Plurmeditekon",
|
||||
"ValueSpecialEpisodeName": "Speciala - {0}",
|
||||
"TaskOptimizeDatabase": "Optimigi datumbazon",
|
||||
"TaskOptimizeDatabase": "Optimumigi datenbazon",
|
||||
"TaskRefreshChannels": "Refreŝigi Kanalojn",
|
||||
"TaskUpdatePlugins": "Ĝisdatigi Kromprogramojn",
|
||||
"TaskRefreshPeople": "Refreŝigi Homojn",
|
||||
"TasksChannelsCategory": "Interretaj Kanaloj",
|
||||
"ProviderValue": "Provizanto: {0}",
|
||||
"NotificationOptionPluginError": "Kromprograma malsukceso",
|
||||
"NotificationOptionPluginError": "Kromprogramo malsukcesis",
|
||||
"MixedContent": "Miksita enhavo",
|
||||
"TasksApplicationCategory": "Aplikaĵo",
|
||||
"TasksMaintenanceCategory": "Prizorgado",
|
||||
@@ -75,7 +75,7 @@
|
||||
"ServerNameNeedsToBeRestarted": "{0} devas esti relanĉita",
|
||||
"NotificationOptionVideoPlayback": "La videoludado lanĉis",
|
||||
"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.",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Serĉas en interreto mankantajn subtekstojn surbaze de metadatena agordaro.",
|
||||
"TaskRefreshPeopleDescription": "Ĝisdatigas metadatenojn por aktoroj kaj reĵisoroj en via plurmediteko.",
|
||||
@@ -102,9 +102,9 @@
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server estis ĝisdatigita al {0}",
|
||||
"MessageApplicationUpdated": "Jellyfin Server estis ĝisdatigita",
|
||||
"TaskRefreshChannelsDescription": "Refreŝigas informon pri interretaj kanaloj.",
|
||||
"TaskDownloadMissingSubtitles": "Elŝutu mankantajn subtekstojn",
|
||||
"TaskDownloadMissingSubtitles": "Elŝuti mankantajn subtekstojn",
|
||||
"TaskCleanTranscode": "Malplenigi Transkodadan Katalogon",
|
||||
"TaskRefreshChapterImages": "Eltiru Ĉapitro-Bildojn",
|
||||
"TaskRefreshChapterImages": "Eltiri Ĉapitraj Bildojn",
|
||||
"TaskCleanCache": "Malplenigi Staplan Katalogon",
|
||||
"TaskCleanActivityLog": "Malplenigi Aktivecan Ĵurnalon",
|
||||
"PluginUpdatedWithName": "{0} estis ĝisdatigita",
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"TaskCleanLogsDescription": "Kustutab logifailid, mis on vanemad kui {0} päeva.",
|
||||
"TaskCleanLogs": "Puhasta logikataloog",
|
||||
"TaskRefreshLibraryDescription": "Otsib meedikogust uusi faile ja värskendab metaandmeid.",
|
||||
"Collections": "Kollektsioonid",
|
||||
"Collections": "Kogumikud",
|
||||
"TaskRefreshLibrary": "Skaneeri meediakogu",
|
||||
"TaskRefreshChapterImagesDescription": "Loob peatükkidega videote jaoks pisipildid.",
|
||||
"TaskRefreshChapterImages": "Eralda peatükipildid",
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"MixedContent": "Conteúdo Misto",
|
||||
"Movies": "Filmes",
|
||||
"Music": "Música",
|
||||
"MusicVideos": "Videoclips",
|
||||
"MusicVideos": "Videoclipes",
|
||||
"NameInstallFailed": "{0} falha na instalação",
|
||||
"NameSeasonNumber": "Temporada {0}",
|
||||
"NameSeasonUnknown": "Temporada Desconhecida",
|
||||
@@ -118,5 +118,7 @@
|
||||
"TaskCleanActivityLog": "Limpar registo de atividade",
|
||||
"Undefined": "Indefinido",
|
||||
"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} - изъято из медиатеки",
|
||||
"LabelIpAddressValue": "IP-адрес: {0}",
|
||||
"LabelRunningTimeValue": "Длительность: {0}",
|
||||
"Latest": "Последнее",
|
||||
"Latest": "Крайнее",
|
||||
"MessageApplicationUpdated": "Jellyfin Server был обновлён",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Конфигурация сервера (раздел {0}) была обновлена",
|
||||
@@ -119,6 +119,6 @@
|
||||
"Undefined": "Не определено",
|
||||
"Forced": "Форсир-ые",
|
||||
"Default": "По умолчанию",
|
||||
"TaskOptimizeDatabaseDescription": "Сжимает базу данных и обрезает свободное место. Выполнение этой задачи после сканирования библиотеки или внесения других изменений, предполагающих модификации базы данных, может повысить производительность.",
|
||||
"TaskOptimizeDatabaseDescription": "Сжимает базу данных и вырезает свободные места. Выполнение этой задачи после сканирования библиотеки или внесения других изменений, предполагающих модификации базы данных, может повысить производительность.",
|
||||
"TaskOptimizeDatabase": "Оптимизировать базу данных"
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"MixedContent": "Zmiešaný obsah",
|
||||
"Movies": "Filmy",
|
||||
"Music": "Hudba",
|
||||
"MusicVideos": "Hudobné videá",
|
||||
"MusicVideos": "Hudobné videoklipy",
|
||||
"NameInstallFailed": "Inštalácia {0} zlyhala",
|
||||
"NameSeasonNumber": "Séria {0}",
|
||||
"NameSeasonUnknown": "Neznáma séria",
|
||||
|
||||
1
Emby.Server.Implementations/Localization/Core/te.json
Normal file
1
Emby.Server.Implementations/Localization/Core/te.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -8,7 +8,7 @@
|
||||
"CameraImageUploadedFrom": "{0} 'den yeni bir kamera resmi yüklendi",
|
||||
"Channels": "Kanallar",
|
||||
"ChapterNameValue": "Bölüm {0}",
|
||||
"Collections": "Koleksiyon",
|
||||
"Collections": "Koleksiyonlar",
|
||||
"DeviceOfflineWithName": "{0} bağlantısı kesildi",
|
||||
"DeviceOnlineWithName": "{0} bağlı",
|
||||
"FailedLoginAttemptWithUserName": "{0} adresinden giriş başarısız oldu",
|
||||
|
||||
@@ -62,11 +62,11 @@
|
||||
"PluginUninstalledWithName": "{0} đã được gỡ bỏ",
|
||||
"PluginInstalledWithName": "{0} đã được cài đặt",
|
||||
"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",
|
||||
"NotificationOptionUserLockedOut": "Người dùng bị khóa",
|
||||
"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",
|
||||
"NotificationOptionPluginUninstalled": "Đã gỡ bỏ Plugin",
|
||||
"NotificationOptionPluginInstalled": "Đã cài đặt Plugin",
|
||||
@@ -75,7 +75,7 @@
|
||||
"NotificationOptionInstallationFailed": "Cài đặt thất bại",
|
||||
"NotificationOptionCameraImageUploaded": "Đã tải lên hình ảnh máy ảnh",
|
||||
"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",
|
||||
"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.",
|
||||
|
||||
1
Emby.Server.Implementations/Localization/Core/zu.json
Normal file
1
Emby.Server.Implementations/Localization/Core/zu.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -372,9 +372,11 @@ namespace Emby.Server.Implementations.Localization
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<LocalizationOption> GetLocalizationOptions()
|
||||
{
|
||||
yield return new LocalizationOption("Afrikaans", "af");
|
||||
yield return new LocalizationOption("Arabic", "ar");
|
||||
yield return new LocalizationOption("Bulgarian (Bulgaria)", "bg-BG");
|
||||
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 Traditional", "zh-TW");
|
||||
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("English (United Kingdom)", "en-GB");
|
||||
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 (Canada)", "fr-CA");
|
||||
yield return new LocalizationOption("German", "de");
|
||||
yield return new LocalizationOption("Greek", "el");
|
||||
yield return new LocalizationOption("Hebrew", "he");
|
||||
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("Japanese", "ja");
|
||||
yield return new LocalizationOption("Kazakh", "kk");
|
||||
yield return new LocalizationOption("Korean", "ko");
|
||||
yield return new LocalizationOption("Latvian", "lv");
|
||||
yield return new LocalizationOption("Lithuanian", "lt-LT");
|
||||
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 Nynorsk", "nn");
|
||||
yield return new LocalizationOption("Persian", "fa");
|
||||
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 (Portugal)", "pt-PT");
|
||||
yield return new LocalizationOption("Romanian", "ro");
|
||||
yield return new LocalizationOption("Russian", "ru");
|
||||
yield return new LocalizationOption("Serbian", "sr");
|
||||
yield return new LocalizationOption("Slovak", "sk");
|
||||
yield return new LocalizationOption("Slovenian (Slovenia)", "sl-SI");
|
||||
yield return new LocalizationOption("Spanish", "es");
|
||||
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("Swedish", "sv");
|
||||
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("Tiếng Việt", "vi");
|
||||
yield return new LocalizationOption("Ukrainian", "uk");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,7 +630,7 @@
|
||||
"TwoLetterISORegionName": "MD"
|
||||
},
|
||||
{
|
||||
"DisplayName": "Réunion",
|
||||
"DisplayName": "Réunion",
|
||||
"Name": "RE",
|
||||
"ThreeLetterISORegionName": "REU",
|
||||
"TwoLetterISORegionName": "RE"
|
||||
|
||||
@@ -349,7 +349,8 @@ pli||pi|Pali|pali
|
||||
pol||pl|Polish|polonais
|
||||
pon|||Pohnpeian|pohnpei
|
||||
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
|
||||
pro|||Provençal, Old (to 1500)|provençal ancien (jusqu'à 1500)
|
||||
pus||ps|Pushto; Pashto|pachto
|
||||
|
||||
@@ -126,7 +126,8 @@ namespace Emby.Server.Implementations.Plugins
|
||||
{
|
||||
assembly = Assembly.LoadFrom(file);
|
||||
|
||||
assembly.GetExportedTypes();
|
||||
// Load all required types to verify that the plugin will load
|
||||
assembly.GetTypes();
|
||||
}
|
||||
catch (FileLoadException ex)
|
||||
{
|
||||
@@ -134,7 +135,7 @@ namespace Emby.Server.Implementations.Plugins
|
||||
ChangePluginState(plugin, PluginStatus.Malfunctioned);
|
||||
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);
|
||||
ChangePluginState(plugin, PluginStatus.NotSupported);
|
||||
|
||||
@@ -638,7 +638,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(Name + ": Cancelling");
|
||||
_logger.LogInformation("{Name}: Cancelling", Name);
|
||||
token.Cancel();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -652,16 +652,16 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(Name + ": Waiting on Task");
|
||||
_logger.LogInformation("{Name}: Waiting on Task", Name);
|
||||
var exited = task.Wait(2000);
|
||||
|
||||
if (exited)
|
||||
{
|
||||
_logger.LogInformation(Name + ": Task exited");
|
||||
_logger.LogInformation("{Name}: Task exited", Name);
|
||||
}
|
||||
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)
|
||||
@@ -674,7 +674,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug(Name + ": Disposing CancellationToken");
|
||||
_logger.LogDebug("{Name}: Disposing CancellationToken", Name);
|
||||
token.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -161,11 +161,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting directory {path}", directory);
|
||||
_logger.LogError(ex, "Error deleting directory {Path}", directory);
|
||||
}
|
||||
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)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting file {path}", path);
|
||||
_logger.LogError(ex, "Error deleting file {Path}", path);
|
||||
}
|
||||
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)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting directory {path}", directory);
|
||||
_logger.LogError(ex, "Error deleting directory {Path}", directory);
|
||||
}
|
||||
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)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting file {path}", path);
|
||||
_logger.LogError(ex, "Error deleting file {Path}", path);
|
||||
}
|
||||
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));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,11 @@ namespace Jellyfin.Api.Auth
|
||||
try
|
||||
{
|
||||
var authorizationInfo = await _authService.Authenticate(Request).ConfigureAwait(false);
|
||||
if (!authorizationInfo.HasToken)
|
||||
{
|
||||
return AuthenticateResult.NoResult();
|
||||
}
|
||||
|
||||
var role = UserRoles.User;
|
||||
if (authorizationInfo.IsApiKey || authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator))
|
||||
{
|
||||
|
||||
149
Jellyfin.Api/Controllers/ClientLogController.cs
Normal file
149
Jellyfin.Api/Controllers/ClientLogController.cs
Normal file
@@ -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 MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -143,21 +143,24 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
existingDisplayPreferences.ScrollDirection = displayPreferences.ScrollDirection;
|
||||
existingDisplayPreferences.ChromecastVersion = displayPreferences.CustomPrefs.TryGetValue("chromecastVersion", out var chromecastVersion)
|
||||
&& !string.IsNullOrEmpty(chromecastVersion)
|
||||
? Enum.Parse<ChromecastVersion>(chromecastVersion, true)
|
||||
: ChromecastVersion.Stable;
|
||||
displayPreferences.CustomPrefs.Remove("chromecastVersion");
|
||||
|
||||
existingDisplayPreferences.EnableNextVideoInfoOverlay = displayPreferences.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enableNextVideoInfoOverlay)
|
||||
? bool.Parse(enableNextVideoInfoOverlay)
|
||||
: true;
|
||||
existingDisplayPreferences.EnableNextVideoInfoOverlay = !displayPreferences.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enableNextVideoInfoOverlay)
|
||||
|| string.IsNullOrEmpty(enableNextVideoInfoOverlay)
|
||||
|| bool.Parse(enableNextVideoInfoOverlay);
|
||||
displayPreferences.CustomPrefs.Remove("enableNextVideoInfoOverlay");
|
||||
|
||||
existingDisplayPreferences.SkipBackwardLength = displayPreferences.CustomPrefs.TryGetValue("skipBackLength", out var skipBackLength)
|
||||
&& !string.IsNullOrEmpty(skipBackLength)
|
||||
? int.Parse(skipBackLength, CultureInfo.InvariantCulture)
|
||||
: 10000;
|
||||
displayPreferences.CustomPrefs.Remove("skipBackLength");
|
||||
|
||||
existingDisplayPreferences.SkipForwardLength = displayPreferences.CustomPrefs.TryGetValue("skipForwardLength", out var skipForwardLength)
|
||||
&& !string.IsNullOrEmpty(skipForwardLength)
|
||||
? int.Parse(skipForwardLength, CultureInfo.InvariantCulture)
|
||||
: 30000;
|
||||
displayPreferences.CustomPrefs.Remove("skipForwardLength");
|
||||
@@ -196,7 +199,7 @@ namespace Jellyfin.Api.Controllers
|
||||
}
|
||||
|
||||
var itemPrefs = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, itemId, existingDisplayPreferences.Client);
|
||||
itemPrefs.SortBy = displayPreferences.SortBy;
|
||||
itemPrefs.SortBy = displayPreferences.SortBy ?? "SortName";
|
||||
itemPrefs.SortOrder = displayPreferences.SortOrder;
|
||||
itemPrefs.RememberIndexing = displayPreferences.RememberIndexing;
|
||||
itemPrefs.RememberSorting = displayPreferences.RememberSorting;
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace Jellyfin.Api.Controllers
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_dlnaManager.UpdateProfile(deviceProfile);
|
||||
_dlnaManager.UpdateProfile(profileId, deviceProfile);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1391,7 +1391,7 @@ namespace Jellyfin.Api.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("Invalid HLS segment container: " + segmentFormat);
|
||||
_logger.LogError("Invalid HLS segment container: {SegmentFormat}", segmentFormat);
|
||||
}
|
||||
|
||||
var maxMuxingQueueSize = _encodingOptions.MaxMuxingQueueSize > 128
|
||||
@@ -1794,7 +1794,7 @@ namespace Jellyfin.Api.Controllers
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Deleting partial HLS file {path}", path);
|
||||
_logger.LogDebug("Deleting partial HLS file {Path}", path);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -1802,15 +1802,15 @@ namespace Jellyfin.Api.Controllers
|
||||
}
|
||||
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);
|
||||
Task.WaitAll(task);
|
||||
task.Wait();
|
||||
DeleteFile(path, retryCount + 1);
|
||||
}
|
||||
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();
|
||||
file = Path.GetFullPath(Path.Combine(transcodePath, 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.");
|
||||
}
|
||||
@@ -90,7 +90,7 @@ namespace Jellyfin.Api.Controllers
|
||||
var transcodePath = _serverConfigurationManager.GetTranscodePath();
|
||||
file = Path.GetFullPath(Path.Combine(transcodePath, 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.");
|
||||
}
|
||||
@@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
file = Path.GetFullPath(Path.Combine(transcodeFolderPath, 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.");
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace Jellyfin.Api.Controllers
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (!path.StartsWith(_applicationPaths.GeneralPath))
|
||||
if (!path.StartsWith(_applicationPaths.GeneralPath, StringComparison.InvariantCulture))
|
||||
{
|
||||
return BadRequest("Invalid image path.");
|
||||
}
|
||||
@@ -177,7 +177,7 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
|
||||
{
|
||||
if (!path.StartsWith(basePath))
|
||||
if (!path.StartsWith(basePath, StringComparison.InvariantCulture))
|
||||
{
|
||||
return BadRequest("Invalid image path.");
|
||||
}
|
||||
@@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
|
||||
{
|
||||
if (!path.StartsWith(basePath))
|
||||
if (!path.StartsWith(basePath, StringComparison.InvariantCulture))
|
||||
{
|
||||
return BadRequest("Invalid image path.");
|
||||
}
|
||||
|
||||
@@ -528,7 +528,7 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
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));
|
||||
}
|
||||
else
|
||||
|
||||
@@ -212,10 +212,13 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <returns>An <see cref="IEnumerable{WakeOnLanInfo}"/> with the WakeOnLan infos.</returns>
|
||||
[HttpGet("WakeOnLanInfo")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[Obsolete("This endpoint is obsolete.")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<IEnumerable<WakeOnLanInfo>> GetWakeOnLanInfo()
|
||||
{
|
||||
var result = _appHost.GetWakeOnLanInfo();
|
||||
var result = _network.GetMacAddresses()
|
||||
.Select(i => new WakeOnLanInfo(i))
|
||||
.ToList();
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Jellyfin.Api.Helpers
|
||||
// If any this null throw an exception.
|
||||
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.
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace Jellyfin.Api.Helpers
|
||||
|
||||
mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId)
|
||||
? 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)
|
||||
{
|
||||
|
||||
@@ -283,6 +283,7 @@ namespace Jellyfin.Api.Helpers
|
||||
|
||||
lock (job.ProcessLock!)
|
||||
{
|
||||
#pragma warning disable CA1849 // Can't await in lock block
|
||||
job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
|
||||
|
||||
var process = job.Process;
|
||||
@@ -308,6 +309,7 @@ namespace Jellyfin.Api.Helpers
|
||||
{
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1849
|
||||
}
|
||||
|
||||
if (delete(job.Path!))
|
||||
@@ -541,8 +543,7 @@ namespace Jellyfin.Api.Helpers
|
||||
state,
|
||||
cancellationTokenSource);
|
||||
|
||||
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
||||
_logger.LogInformation(commandLineLogMessage);
|
||||
_logger.LogInformation("{Filename} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
|
||||
var logFilePrefix = "FFmpeg.Transcode-";
|
||||
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.
|
||||
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);
|
||||
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);
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.2.3" />
|
||||
</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; }
|
||||
}
|
||||
}
|
||||
30
Jellyfin.Api/Models/ClientLogDtos/ClientLogEventDto.cs
Normal file
30
Jellyfin.Api/Models/ClientLogDtos/ClientLogEventDto.cs
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code analysers-->
|
||||
@@ -35,7 +35,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -455,10 +455,10 @@ namespace Jellyfin.Networking.Manager
|
||||
}
|
||||
|
||||
// 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/>
|
||||
@@ -481,7 +481,7 @@ namespace Jellyfin.Networking.Manager
|
||||
}
|
||||
|
||||
// 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/>
|
||||
@@ -647,16 +647,6 @@ namespace Jellyfin.Networking.Manager
|
||||
_interfaceAddresses.AddItem(address, false);
|
||||
_interfaceNames[parts[2]] = Math.Abs(index);
|
||||
}
|
||||
|
||||
if (IsIP4Enabled)
|
||||
{
|
||||
_interfaceAddresses.AddItem(IPNetAddress.IP4Loopback);
|
||||
}
|
||||
|
||||
if (IsIP6Enabled)
|
||||
{
|
||||
_interfaceAddresses.AddItem(IPNetAddress.IP6Loopback);
|
||||
}
|
||||
}
|
||||
|
||||
InitialiseLAN(config);
|
||||
@@ -1037,17 +1027,14 @@ namespace Jellyfin.Networking.Manager
|
||||
// Subnets are the same as the calculated internal interface.
|
||||
_lanSubnets = new Collection<IPObject>();
|
||||
|
||||
// We must listen on loopback for LiveTV to function regardless of the settings.
|
||||
if (IsIP6Enabled)
|
||||
{
|
||||
_lanSubnets.AddItem(IPNetAddress.IP6Loopback);
|
||||
_lanSubnets.AddItem(IPNetAddress.Parse("fc00::/7")); // ULA
|
||||
_lanSubnets.AddItem(IPNetAddress.Parse("fe80::/10")); // Site local
|
||||
}
|
||||
|
||||
if (IsIP4Enabled)
|
||||
{
|
||||
_lanSubnets.AddItem(IPNetAddress.IP4Loopback);
|
||||
_lanSubnets.AddItem(IPNetAddress.Parse("10.0.0.0/8"));
|
||||
_lanSubnets.AddItem(IPNetAddress.Parse("172.16.0.0/12"));
|
||||
_lanSubnets.AddItem(IPNetAddress.Parse("192.168.0.0/16"));
|
||||
@@ -1055,17 +1042,6 @@ namespace Jellyfin.Networking.Manager
|
||||
}
|
||||
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.
|
||||
_internalInterfaces = CreateCollection(_interfaceAddresses.Where(IsInLocalNetwork));
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Jellyfin.Server.Implementations.Events
|
||||
}
|
||||
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>
|
||||
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0-rc.2*">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0-rc.2*">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -185,9 +185,21 @@ namespace Jellyfin.Server.Implementations.Security
|
||||
authInfo.IsAuthenticated = true;
|
||||
authInfo.Client = key.Name;
|
||||
authInfo.Token = key.AccessToken;
|
||||
authInfo.DeviceId = string.Empty;
|
||||
authInfo.Device = string.Empty;
|
||||
authInfo.Version = string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
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="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="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(
|
||||
IServerApplicationPaths applicationPaths,
|
||||
ILoggerFactory loggerFactory,
|
||||
IStartupOptions options,
|
||||
IConfiguration startupConfig,
|
||||
IFileSystem fileSystem,
|
||||
IServiceCollection collection)
|
||||
IConfiguration startupConfig)
|
||||
: base(
|
||||
applicationPaths,
|
||||
loggerFactory,
|
||||
options,
|
||||
startupConfig,
|
||||
fileSystem,
|
||||
collection)
|
||||
startupConfig)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void RegisterServices()
|
||||
protected override void RegisterServices(IServiceCollection serviceCollection)
|
||||
{
|
||||
// Register an image encoder
|
||||
bool useSkiaEncoder = SkiaEncoder.IsNativeLibAvailable();
|
||||
Type imageEncoderType = useSkiaEncoder
|
||||
? typeof(SkiaEncoder)
|
||||
: typeof(NullImageEncoder);
|
||||
ServiceCollection.AddSingleton(typeof(IImageEncoder), imageEncoderType);
|
||||
serviceCollection.AddSingleton(typeof(IImageEncoder), imageEncoderType);
|
||||
|
||||
// Log a warning if the Skia encoder could not be used
|
||||
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
|
||||
.UseLoggerFactory(LoggerFactory)
|
||||
.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"));
|
||||
|
||||
ServiceCollection.AddEventServices();
|
||||
ServiceCollection.AddSingleton<IBaseItemManager, BaseItemManager>();
|
||||
ServiceCollection.AddSingleton<IEventManager, EventManager>();
|
||||
ServiceCollection.AddSingleton<JellyfinDbProvider>();
|
||||
serviceCollection.AddEventServices();
|
||||
serviceCollection.AddSingleton<IBaseItemManager, BaseItemManager>();
|
||||
serviceCollection.AddSingleton<IEventManager, EventManager>();
|
||||
serviceCollection.AddSingleton<JellyfinDbProvider>();
|
||||
|
||||
ServiceCollection.AddSingleton<IActivityManager, ActivityManager>();
|
||||
ServiceCollection.AddSingleton<IUserManager, UserManager>();
|
||||
ServiceCollection.AddSingleton<IDisplayPreferencesManager, DisplayPreferencesManager>();
|
||||
ServiceCollection.AddSingleton<IDeviceManager, DeviceManager>();
|
||||
serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
|
||||
serviceCollection.AddSingleton<IUserManager, UserManager>();
|
||||
serviceCollection.AddSingleton<IDisplayPreferencesManager, DisplayPreferencesManager>();
|
||||
serviceCollection.AddSingleton<IDeviceManager, DeviceManager>();
|
||||
|
||||
// TODO search the assemblies instead of adding them manually?
|
||||
ServiceCollection.AddSingleton<IWebSocketListener, SessionWebSocketListener>();
|
||||
ServiceCollection.AddSingleton<IWebSocketListener, ActivityLogWebSocketListener>();
|
||||
ServiceCollection.AddSingleton<IWebSocketListener, ScheduledTasksWebSocketListener>();
|
||||
ServiceCollection.AddSingleton<IWebSocketListener, SessionInfoWebSocketListener>();
|
||||
serviceCollection.AddSingleton<IWebSocketListener, SessionWebSocketListener>();
|
||||
serviceCollection.AddSingleton<IWebSocketListener, ActivityLogWebSocketListener>();
|
||||
serviceCollection.AddSingleton<IWebSocketListener, ScheduledTasksWebSocketListener>();
|
||||
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 />
|
||||
|
||||
@@ -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>
|
||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" 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" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.0" />
|
||||
<PackageReference Include="prometheus-net" Version="5.0.1" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore" Version="5.0.1" />
|
||||
<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.File" Version="5.0.0" />
|
||||
<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>
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace Jellyfin.Server.Middleware
|
||||
if (_enableWarning && watch.ElapsedMilliseconds > _warningThreshold)
|
||||
{
|
||||
_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.GetNormalizedRemoteIp(),
|
||||
watch.Elapsed,
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Jellyfin.Server.Middleware
|
||||
return;
|
||||
}
|
||||
|
||||
if (!key.Contains('='))
|
||||
if (!key.Contains('=', StringComparison.Ordinal))
|
||||
{
|
||||
_store = value;
|
||||
return;
|
||||
|
||||
@@ -9,7 +9,7 @@ using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
@@ -114,6 +114,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
}
|
||||
|
||||
var chromecastVersion = dto.CustomPrefs.TryGetValue("chromecastVersion", out var version)
|
||||
&& !string.IsNullOrEmpty(version)
|
||||
? chromecastDict[version]
|
||||
: ChromecastVersion.Stable;
|
||||
dto.CustomPrefs.Remove("chromecastVersion");
|
||||
|
||||
@@ -10,10 +10,10 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommandLine;
|
||||
using Emby.Server.Implementations;
|
||||
using Emby.Server.Implementations.IO;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.ClientEvent;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
@@ -26,6 +26,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Serilog;
|
||||
using Serilog.Extensions.Logging;
|
||||
using Serilog.Filters;
|
||||
using SQLitePCL;
|
||||
using ConfigurationExtensions = MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
@@ -157,34 +158,36 @@ namespace Jellyfin.Server
|
||||
|
||||
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();
|
||||
var serviceCollection = new ServiceCollection();
|
||||
|
||||
var appHost = new CoreAppHost(
|
||||
appPaths,
|
||||
_loggerFactory,
|
||||
options,
|
||||
startupConfig,
|
||||
new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
|
||||
serviceCollection);
|
||||
startupConfig);
|
||||
|
||||
try
|
||||
{
|
||||
// If hosting the web client, validate the client content path
|
||||
if (startupConfig.HostWebClient())
|
||||
{
|
||||
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 serviceCollection = new ServiceCollection();
|
||||
appHost.Init(serviceCollection);
|
||||
|
||||
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
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.ReadFrom.Configuration(configuration)
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.WithThreadId()
|
||||
.WriteTo.Logger(lc =>
|
||||
lc.ReadFrom.Configuration(configuration)
|
||||
.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();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}")
|
||||
.WriteTo.Async(x => x.File(
|
||||
Path.Combine(appPaths.LogDirectoryPath, "log_.log"),
|
||||
rollingInterval: RollingInterval.Day,
|
||||
outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}",
|
||||
encoding: Encoding.UTF8))
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.WithThreadId()
|
||||
.WriteTo.Logger(lc =>
|
||||
lc.WriteTo.Async(x => x.File(
|
||||
Path.Combine(appPaths.LogDirectoryPath, "log_.log"),
|
||||
rollingInterval: RollingInterval.Day,
|
||||
outputTemplate: "{Message:l}{NewLine}{Exception}",
|
||||
encoding: Encoding.UTF8))
|
||||
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}")
|
||||
.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();
|
||||
|
||||
Log.Logger.Fatal(ex, "Failed to create/read logger configuration");
|
||||
@@ -648,7 +675,7 @@ namespace Jellyfin.Server
|
||||
|
||||
private static string NormalizeCommandLineArgument(string arg)
|
||||
{
|
||||
if (!arg.Contains(" ", StringComparison.OrdinalIgnoreCase))
|
||||
if (!arg.Contains(' ', StringComparison.Ordinal))
|
||||
{
|
||||
return arg;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace MediaBrowser.Common
|
||||
{
|
||||
@@ -137,13 +138,7 @@ namespace MediaBrowser.Common
|
||||
/// <summary>
|
||||
/// Initializes this instance.
|
||||
/// </summary>
|
||||
void Init();
|
||||
|
||||
/// <summary>
|
||||
/// Creates the instance.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
object CreateInstance(Type type);
|
||||
/// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param>
|
||||
void Init(IServiceCollection serviceCollection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
55
MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs
Normal file
55
MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
MediaBrowser.Controller/ClientEvent/IClientEventLogger.cs
Normal file
31
MediaBrowser.Controller/ClientEvent/IClientEventLogger.cs
Normal file
@@ -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>
|
||||
/// Updates the profile.
|
||||
/// </summary>
|
||||
/// <param name="profileId">The profile id.</param>
|
||||
/// <param name="profile">The profile.</param>
|
||||
void UpdateProfile(DeviceProfile profile);
|
||||
void UpdateProfile(string profileId, DeviceProfile profile);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the profile.
|
||||
|
||||
@@ -84,8 +84,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
Model.Entities.ExtraType.Scene
|
||||
};
|
||||
|
||||
public static readonly char[] SlugReplaceChars = { '?', '/', '&' };
|
||||
|
||||
/// <summary>
|
||||
/// The supported extra folder names and types. See <see cref="Emby.Naming.Common.NamingOptions" />.
|
||||
/// </summary>
|
||||
@@ -354,11 +352,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
get
|
||||
{
|
||||
// if (IsOffline)
|
||||
// {
|
||||
// return LocationType.Offline;
|
||||
// }
|
||||
|
||||
var path = Path;
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
@@ -391,7 +384,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsFileProtocol => IsPathProtocol(MediaProtocol.File);
|
||||
public bool IsFileProtocol => PathProtocol == MediaProtocol.File;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool HasPathProtocol => PathProtocol.HasValue;
|
||||
@@ -583,14 +576,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Guid DisplayParentId
|
||||
{
|
||||
get
|
||||
{
|
||||
var parentId = ParentId;
|
||||
return parentId;
|
||||
}
|
||||
}
|
||||
public virtual Guid DisplayParentId => ParentId;
|
||||
|
||||
[JsonIgnore]
|
||||
public BaseItem DisplayParent
|
||||
@@ -853,13 +839,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
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)
|
||||
{
|
||||
var list = new List<Tuple<StringBuilder, bool>>();
|
||||
@@ -987,7 +966,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
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>
|
||||
@@ -1302,8 +1281,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
terms.Add(item.Name);
|
||||
}
|
||||
|
||||
var video = item as Video;
|
||||
if (video != null)
|
||||
if (item is Video video)
|
||||
{
|
||||
if (video.Video3DFormat.HasValue)
|
||||
{
|
||||
@@ -1338,7 +1316,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join('/', terms.ToArray());
|
||||
return string.Join('/', terms);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1361,9 +1339,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
.Select(audio =>
|
||||
{
|
||||
// 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 (dbItem != null)
|
||||
if (LibraryManager.GetItemById(audio.Id) is Audio.Audio dbItem)
|
||||
{
|
||||
audio = dbItem;
|
||||
}
|
||||
@@ -1476,7 +1452,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
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 (hasTrailers != null)
|
||||
if (this is IHasTrailers hasTrailers)
|
||||
{
|
||||
localTrailersChanged = await RefreshLocalTrailers(hasTrailers, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -2268,7 +2243,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
var existingImage = GetImageInfo(image.Type, index);
|
||||
|
||||
if (existingImage != null)
|
||||
if (existingImage == null)
|
||||
{
|
||||
AddImage(image);
|
||||
}
|
||||
else
|
||||
{
|
||||
existingImage.Path = image.Path;
|
||||
existingImage.DateModified = image.DateModified;
|
||||
@@ -2276,15 +2255,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
existingImage.Height = image.Height;
|
||||
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)
|
||||
@@ -2298,7 +2268,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (image == null)
|
||||
{
|
||||
ImageInfos = ImageInfos.Concat(new[] { GetImageInfo(file, type) }).ToArray();
|
||||
AddImage(GetImageInfo(file, type));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2342,14 +2312,24 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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)
|
||||
=> LibraryManager.UpdateItemAsync(this, GetParent(), updateReason, cancellationToken);
|
||||
|
||||
@@ -2373,7 +2353,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (deletedImages.Count > 0)
|
||||
{
|
||||
ImageInfos = ImageInfos.Except(deletedImages).ToArray();
|
||||
RemoveImages(deletedImages);
|
||||
}
|
||||
|
||||
return deletedImages.Count > 0;
|
||||
@@ -2495,11 +2475,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the images.
|
||||
/// Adds the images, updating metadata if they already are part of this item.
|
||||
/// </summary>
|
||||
/// <param name="imageType">Type of the image.</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>
|
||||
public bool AddImages(ImageType imageType, List<FileSystemMetadata> images)
|
||||
{
|
||||
@@ -2512,7 +2492,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
.ToList();
|
||||
|
||||
var newImageList = new List<FileSystemMetadata>();
|
||||
var imageAdded = false;
|
||||
var imageUpdated = false;
|
||||
|
||||
foreach (var newImage in images)
|
||||
@@ -2528,7 +2507,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
if (existing == null)
|
||||
{
|
||||
newImageList.Add(newImage);
|
||||
imageAdded = true;
|
||||
}
|
||||
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)
|
||||
{
|
||||
ImageInfos = ImageInfos.Concat(newImageList.Select(i => GetImageInfo(i, imageType))).ToArray();
|
||||
@@ -2612,7 +2577,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
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)
|
||||
@@ -2730,7 +2695,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -2758,8 +2723,10 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
protected Task RefreshMetadataForOwnedItem(BaseItem ownedItem, bool copyTitleMetadata, MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
var newOptions = new MetadataRefreshOptions(options);
|
||||
newOptions.SearchResult = null;
|
||||
var newOptions = new MetadataRefreshOptions(options)
|
||||
{
|
||||
SearchResult = null
|
||||
};
|
||||
|
||||
var item = this;
|
||||
|
||||
@@ -2820,8 +2787,10 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
protected Task RefreshMetadataForOwnedVideo(MetadataRefreshOptions options, bool copyTitleMetadata, string path, CancellationToken cancellationToken)
|
||||
{
|
||||
var newOptions = new MetadataRefreshOptions(options);
|
||||
newOptions.SearchResult = null;
|
||||
var newOptions = new MetadataRefreshOptions(options)
|
||||
{
|
||||
SearchResult = null
|
||||
};
|
||||
|
||||
var id = LibraryManager.GetNewItemId(path, typeof(Video));
|
||||
|
||||
@@ -2835,14 +2804,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
newOptions.ForceSave = true;
|
||||
}
|
||||
|
||||
// var parentId = Id;
|
||||
// if (!video.IsOwnedItem || video.ParentId != parentId)
|
||||
// {
|
||||
// video.IsOwnedItem = true;
|
||||
// video.ParentId = parentId;
|
||||
// newOptions.ForceSave = true;
|
||||
// }
|
||||
|
||||
if (video == null)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
@@ -2926,7 +2887,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
.Select(i => i.OfficialRating)
|
||||
.Where(i => !string.IsNullOrEmpty(i))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.Select(i => new Tuple<string, int?>(i, LocalizationManager.GetRatingLevel(i)))
|
||||
.Select(i => (i, LocalizationManager.GetRatingLevel(i)))
|
||||
.OrderBy(i => i.Item2 ?? 1000)
|
||||
.Select(i => i.Item1);
|
||||
|
||||
@@ -2973,18 +2934,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
.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()
|
||||
{
|
||||
return RunTimeTicks ?? 0;
|
||||
|
||||
@@ -303,7 +303,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
if (dictionary.ContainsKey(id))
|
||||
{
|
||||
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,
|
||||
child.Path ?? child.Name);
|
||||
}
|
||||
@@ -425,7 +425,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
if (item.IsFileProtocol)
|
||||
{
|
||||
Logger.LogDebug("Removed item: " + item.Path);
|
||||
Logger.LogDebug("Removed item: {Path}", item.Path);
|
||||
|
||||
item.SetParent(null);
|
||||
LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }, this, false);
|
||||
@@ -807,7 +807,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1013,6 +1013,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
items = CollapseBoxSetItemsIfNeeded(items, query, this, user, ConfigurationManager, CollectionManager);
|
||||
}
|
||||
|
||||
#pragma warning disable CA1309
|
||||
if (!string.IsNullOrEmpty(query.NameStartsWithOrGreater))
|
||||
{
|
||||
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);
|
||||
}
|
||||
#pragma warning restore CA1309
|
||||
|
||||
// This must be the last filter
|
||||
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))
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace MediaBrowser.Controller.IO
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -42,11 +42,6 @@ namespace MediaBrowser.Controller
|
||||
/// <value>The name of the friendly.</value>
|
||||
string FriendlyName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configured published server url.
|
||||
/// </summary>
|
||||
string PublishedServerUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system info.
|
||||
/// </summary>
|
||||
@@ -60,32 +55,29 @@ namespace MediaBrowser.Controller
|
||||
/// Gets a URL specific for the request.
|
||||
/// </summary>
|
||||
/// <param name="request">The <see cref="HttpRequest"/> instance.</param>
|
||||
/// <param name="port">Optional port number.</param>
|
||||
/// <returns>An accessible URL.</returns>
|
||||
string GetSmartApiUrl(HttpRequest request, int? port = null);
|
||||
string GetSmartApiUrl(HttpRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a URL specific for the request.
|
||||
/// </summary>
|
||||
/// <param name="remoteAddr">The remote <see cref="IPAddress"/> of the connection.</param>
|
||||
/// <param name="port">Optional port number.</param>
|
||||
/// <returns>An accessible URL.</returns>
|
||||
string GetSmartApiUrl(IPAddress remoteAddr, int? port = null);
|
||||
string GetSmartApiUrl(IPAddress remoteAddr);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a URL specific for the request.
|
||||
/// </summary>
|
||||
/// <param name="hostname">The hostname used in the connection.</param>
|
||||
/// <param name="port">Optional port number.</param>
|
||||
/// <returns>An accessible URL.</returns>
|
||||
string GetSmartApiUrl(string hostname, int? port = null);
|
||||
string GetSmartApiUrl(string hostname);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a localhost URL that can be used to access the API using the loop-back IP address.
|
||||
/// over HTTP (not HTTPS).
|
||||
/// Gets an URL that can be used to access the API over LAN.
|
||||
/// </summary>
|
||||
/// <param name="allowHttps">A value indicating whether to allow HTTPS.</param>
|
||||
/// <returns>The API URL.</returns>
|
||||
string GetLoopbackHttpApiUrl();
|
||||
string GetApiUrlForLocalAccess(bool allowHttps = true);
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
string GetLocalApiUrl(string hostname, string scheme = null, int? port = null);
|
||||
|
||||
IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo();
|
||||
|
||||
string ExpandVirtualPath(string path);
|
||||
|
||||
string ReverseVirtualPath(string path);
|
||||
|
||||
@@ -30,13 +30,6 @@ namespace MediaBrowser.Controller.Library
|
||||
/// <returns>IEnumerable<MediaStream>.</returns>
|
||||
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>
|
||||
/// Gets the media streams.
|
||||
/// </summary>
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Diacritics" Version="3.3.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="System.Threading.Tasks.Dataflow" 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" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0" PrivateAssets="All" />
|
||||
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="6.0.0" />
|
||||
</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