Compare commits

...

181 Commits

Author SHA1 Message Date
Joshua M. Boniface 761a4e8415 Merge pull request #6837 from crobibero/auth-exception 2021-11-13 15:31:14 -05:00
Joshua M. Boniface f0028c728f Merge pull request #6841 from joshuaboniface/azure-pre 2021-11-13 15:28:10 -05:00
Joshua M. Boniface fb0f3c3a76 Send SourceBranch to collect-server.azure.sh 2021-11-13 14:45:02 -05:00
Claus Vium 2f92ee374a Merge pull request #6838 from crobibero/openapi-nullable
Add nullable dictionary openapi mapping
2021-11-13 19:32:11 +01:00
Cody Robibero bb377b1466 Add nullable dictionary openapi mapping 2021-11-13 07:29:58 -07:00
Cody Robibero 4a28f46cac Don't throw exception on unauthenticated requests 2021-11-13 07:27:28 -07:00
Cody Robibero 8868b34d78 Merge pull request #6822 from NickSica/master 2021-11-13 07:03:40 -07:00
Cody Robibero 01a1209f0e Merge pull request #6836 from cvium/apphost_light_cleanup 2021-11-13 07:02:39 -07:00
cvium 5a65bc1e69 Very light cleanup in applicationhost 2021-11-13 14:37:26 +01:00
Nicholas Sica 8ae5316198 Fix Ombi auth through Jellyfin 2021-11-12 16:23:58 -05:00
WWWesten 1fbe1266e2 Added translation using Weblate (Telugu) 2021-11-12 12:56:18 -05:00
WWWesten 412ae7f4d2 Added translation using Weblate (Zulu) 2021-11-12 12:55:27 -05:00
WWWesten 26001fca93 Added translation using Weblate (Belarusian) 2021-11-12 12:09:25 -05:00
Claus Vium 8579c8f9ce Merge pull request #6830 from crobibero/remote-image
Fix filtering images without dimensions
2021-11-12 16:32:53 +01:00
Claus Vium fcfc774da1 Merge pull request #6829 from crobibero/obsolete-enum
Remove Obsolete attribute on enum
2021-11-12 15:46:17 +01:00
Cody Robibero 14c072dd32 Fix filtering images without dimensions 2021-11-12 07:21:46 -07:00
Cody Robibero 132440c683 Remove Obsolete attribute on enum 2021-11-12 06:58:18 -07:00
Claus Vium 7b1314aff5 Merge pull request #6823 from crobibero/displaypreferencesdto
Fix nullability on DisplayPreferencesDto
2021-11-11 19:29:49 +01:00
Nicholas Sica c5e42ddcc6 Fix Ombi auth through Jellyfin
Co-authored-by: Cody Robibero <cody@robibe.ro>
2021-11-11 11:03:27 -05:00
Cody Robibero 3de86ffdb4 Fix nullability on DisplayPreferencesDto
Remove duplicate, fix namespace
2021-11-11 07:34:36 -07:00
NickSica 4b2c40f717 Fixes Ombi auth through Jellyfin 2021-11-11 00:43:43 -05:00
Cody Robibero 4c88bf3fe3 Merge pull request #6808 from cvium/dlnope_never_again 2021-11-09 20:34:09 -07:00
Cody Robibero 57d5423d64 Merge pull request #6816 from Bond-009/warn55
Fix some warnings
2021-11-09 18:36:00 -07:00
cvium efa76c0b63 Remove unused field 2021-11-09 23:17:27 +01:00
cvium 3f09fb8d70 length 2021-11-09 22:45:34 +01:00
Bond_009 1d19a5be61 Fix some warnings
down to 580
2021-11-09 22:29:33 +01:00
Cody Robibero a7a6a22109 Merge pull request #6814 from thornbill/fix-issue-template 2021-11-09 13:21:39 -07:00
Bill Thornton 97508c6f42 Fix yaml format issue in issue template 2021-11-09 15:17:10 -05:00
Bond-009 296ba26b19 Merge pull request #6507 from Artiume/patch-6 2021-11-09 19:56:35 +01:00
Claus Vium c3523e7cf7 Merge pull request #5905 from BaronGreenback/TVFix
Fix for Livetv and DLNA when bind interfaces specified.
2021-11-09 19:53:21 +01:00
Claus Vium c50c9c3dbf Merge pull request #6794 from Bond-009/dead
Remove some dead code
2021-11-09 19:47:34 +01:00
cvium a90735bc5a Last small fixes 2021-11-09 19:44:21 +01:00
cvium 37a04d5dbf Reduce indentation 2021-11-09 19:40:36 +01:00
cvium e1f7f1405e Use GetOrderBy in GetChildrenSorted 2021-11-09 19:33:15 +01:00
cvium 6985a4f255 Fix SortCriteria and refactor SetSorting 2021-11-09 19:31:54 +01:00
cvium 53c16c2342 Build an array instead of using LINQ 2021-11-09 19:22:16 +01:00
cvium c1c77c8762 comments 2021-11-09 19:19:48 +01:00
WWWesten 6bd108877e Translated using Weblate (Russian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ru/
2021-11-09 12:45:38 -05:00
Bond_009 994101fcf4 Remove some dead code 2021-11-09 16:28:39 +01:00
Bond-009 1cfd11b98c Merge pull request #6811 from jellyfin/dependabot/nuget/Mono.Nat-3.0.2 2021-11-09 16:24:13 +01:00
cvium 2b02b53fc0 Merge branch 'master' into dlnope_never_again 2021-11-09 16:00:14 +01:00
cvium 996500b2f8 review stuff 2021-11-09 15:57:39 +01:00
dependabot[bot] ce1c36dbf2 Bump Mono.Nat from 3.0.1 to 3.0.2
Bumps [Mono.Nat](https://github.com/mono/Mono.Nat) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/mono/Mono.Nat/releases)
- [Commits](https://github.com/mono/Mono.Nat/compare/release-v3.0.1...release-v3.0.2)

---
updated-dependencies:
- dependency-name: Mono.Nat
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-09 14:26:19 +00:00
Claus Vium 04040b25e0 Merge pull request #6810 from jellyfin/dependabot/nuget/Microsoft.SourceLink.GitHub-1.1.0
Bump Microsoft.SourceLink.GitHub from 1.0.0 to 1.1.0
2021-11-09 15:25:30 +01:00
Claus Vium 66912deb84 Apply suggestions from code review 2021-11-09 14:47:42 +01:00
Claus Vium ac06022e0f Update Emby.Dlna/ContentDirectory/ControlHandler.cs 2021-11-09 14:40:51 +01:00
dependabot[bot] c2d99dc3f0 Bump Microsoft.SourceLink.GitHub from 1.0.0 to 1.1.0
Bumps [Microsoft.SourceLink.GitHub](https://github.com/dotnet/sourcelink) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/dotnet/sourcelink/releases)
- [Commits](https://github.com/dotnet/sourcelink/compare/1.0.0...1.1.0)

---
updated-dependencies:
- dependency-name: Microsoft.SourceLink.GitHub
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-09 13:11:42 +00:00
Claus Vium ca6211ad61 Merge pull request #6813 from Bond-009/warn54
Fix some warnings
2021-11-09 14:02:38 +01:00
Bond_009 5726535a26 Fix some warnings
609 left
2021-11-09 13:14:31 +01:00
Claus Vium 6f85e30475 Merge pull request #6806 from crobibero/dotnet6
Update to full dotnet 6
2021-11-09 08:35:33 +01:00
cvium a236f52c31 Simplify and reduce LOC in ControlHandler 2021-11-08 23:13:12 +01:00
Cody Robibero 64652b6392 Fix and disable new dotnet6 warnings 2021-11-08 12:40:52 -07:00
hoanghuy309 958a4f509c Translated using Weblate (Vietnamese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/
2021-11-08 13:30:56 -05:00
Cody Robibero 40045d2147 Update to full dotnet 6 2021-11-08 09:40:12 -07:00
Cody Robibero 65971eb27e Merge pull request #6795 from LinFor/pr-dlna
Fix DLNA on older Samsung TVs
2021-11-08 05:38:22 -07:00
rimasx 15dd23e4da Translated using Weblate (Estonian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/et/
2021-11-08 04:58:49 -05:00
cvium 1c47211a9e Merge remote-tracking branch 'BaronGreenback/TVFix' into TVFix 2021-11-08 10:58:28 +01:00
cvium 82e6a21f3b Use the new method in DLNA 2021-11-08 10:58:04 +01:00
cvium f03e77a4d5 Merge branch 'master' into TVFix 2021-11-08 10:38:08 +01:00
LinFor 5c69d110cc Samsung DLNA fixes 2021-11-08 11:24:33 +03:00
Claus Vium 3906343c91 Merge pull request #6793 from Bond-009/rootedpath
Add some docs and tests
2021-11-08 07:49:36 +01:00
Claus Vium 0ee43f897b Merge pull request #5918 from crobibero/client-logger
Add endpoint to log client events
2021-11-08 07:46:51 +01:00
Bond_009 4dfb7b18ae Add some docs and tests 2021-11-07 22:32:08 +01:00
Cody Robibero 666e95e27f Add randomization to generated filename 2021-11-07 11:41:56 -07:00
Claus Vium 195831ad4a Merge pull request #6792 from Bond-009/authex
Fix UnauthorizedAccessException in GetDrives
2021-11-07 19:36:09 +01:00
Cody Robibero 892b05c5e6 Clean up redundant code 2021-11-07 08:20:11 -07:00
Bond_009 0f52896691 Fix UnauthorizedAccessException in GetDrives
```
[15:01:24] [ERR] [55] Jellyfin.Server.Middleware.ExceptionMiddleware: Error processing request. URL GET /Environment/Drives.
System.UnauthorizedAccessException: Access to the path is denied.
 ---> System.IO.IOException: Operation not permitted
   --- End of inner exception stack trace ---
   at System.IO.DriveInfo.CheckStatfsResultAndThrowIfNecessary(Int32 result)
   at System.IO.DriveInfo.get_TotalSize()
   at Emby.Server.Implementations.IO.ManagedFileSystem.<>c.<GetDrives>b__32_0(DriveInfo d) in /home/bond/dev/jellyfin/Emby.Server.Implementations/IO/ManagedFileSystem.cs:line 583
   at System.Linq.Enumerable.WhereSelectArrayIterator`2.ToList()
   at Emby.Server.Implementations.IO.ManagedFileSystem.GetDrives() in /home/bond/dev/jellyfin/Emby.Server.Implementations/IO/ManagedFileSystem.cs:line 583
   at Jellyfin.Api.Controllers.EnvironmentController.GetDrives() in /home/bond/dev/jellyfin/Jellyfin.Api/Controllers/EnvironmentController.cs:line 153
   at lambda_method559(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
```
2021-11-07 15:33:39 +01:00
Bond-009 83859a1e6d Merge pull request #6783 from 1337joe/purge-screenshot 2021-11-06 23:07:11 +01:00
Bond-009 8d8e113771 Merge pull request #6791 from cvium/fix_plugin_loading 2021-11-06 23:06:08 +01:00
cvium d95c281142 Load all types when checking plugin DLLs 2021-11-06 22:44:05 +01:00
Cody Robibero b217f84d50 Merge pull request #6778 from jvoisin/patch-1
Add a bit of hardening to the systemd service
2021-11-06 15:21:52 -06:00
Joe Rogers b4bf5af7c8 Remove ImageType.Screenshot and ItemFields.Screenshot 2021-11-06 20:43:20 +01:00
Cody Robibero c8bd676b14 Merge remote-tracking branch 'upstream/master' into client-logger 2021-11-05 22:25:48 -06:00
Cody Robibero 3c69283e2c Merge pull request #6784 from nielsvanvelzen/openapi-head-checkout
Fix OpenAPI workflow fork cloning
2021-11-05 17:50:58 -06:00
Niels van Velzen 07b9ba2bb4 Set GITHUB_TOKEN permissions to read only in OpenAPI workflow 2021-11-05 22:43:09 +01:00
Niels van Velzen 2491dd513c Specify repository info in openapi head checkout 2021-11-05 22:12:43 +01:00
Cody Robibero d3d9311f48 Merge remote-tracking branch 'upstream/master' into client-logger 2021-11-05 13:12:37 -06:00
Cody Robibero 17264a6020 Use client info from claims 2021-11-05 12:57:24 -06:00
Claus Vium b64d9bcd40 Merge pull request #6782 from nielsvanvelzen/openapi-pr-target 2021-11-05 19:37:30 +01:00
Niels van Velzen 44dc647adb Fix OpenAPI workflow not working with pull requests from forks 2021-11-05 19:10:40 +01:00
Cody Robibero 0cd817bba3 Merge pull request #6781 from 1337joe/tmdb-settings
Add MaxCastMembers to TMDb Plugin Settings
2021-11-05 05:56:51 -06:00
WWWesten 1e93c6ae30 Translated using Weblate (Esperanto)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eo/
2021-11-05 07:05:36 -04:00
WWWesten 13668a6ecb Translated using Weblate (Russian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ru/
2021-11-05 07:05:36 -04:00
Joe Rogers c8eba90c17 Add cast limit to tmdb plugin settings 2021-11-05 00:38:50 +01:00
Joe Rogers 6c76d30538 Add missing checkboxes 2021-11-04 23:58:32 +01:00
Claus Vium a870f4ce70 Merge pull request #6777 from ThibaultNocchi/fix/webvtt_region 2021-11-04 23:37:36 +01:00
Bond-009 e3e6953c6d Merge pull request #6774 from cvium/fix_no_thumb_in_fanart 2021-11-04 17:50:33 +01:00
Julien Voisin 564990964d Add a bit of hardening to the systemd service
Tested in an unprivileged lxc container, so it shouldn't™ break anything.
2021-11-04 16:15:42 +01:00
Thibault Nocchi f91839dd8c Fix WebVTT region to spec 2021-11-04 15:44:15 +01:00
cvium c0bab5c173 Make sure ReadToDescendant was successful, #6773 2021-11-04 11:50:46 +01:00
WWWesten 5aadf8c291 Translated using Weblate (Esperanto)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eo/
2021-11-04 04:56:26 -04:00
WWWesten 654bd6fff1 Translated using Weblate (Turkish)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/tr/
2021-11-04 04:56:26 -04:00
nextlooper42 70b9f9bf56 Translated using Weblate (Slovak)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sk/
2021-11-04 04:56:26 -04:00
Claus Vium ad7ed95b98 Merge pull request #6769 from Bond-009/hasscreenshots 2021-11-04 09:21:36 +01:00
Bond_009 924c6682b9 Remove unused IHasScreenshots interface 2021-11-04 01:06:21 +01:00
Claus Vium 5c5d49ee60 Merge pull request #6763 from 1337joe/tmdb-image-provider-dedup 2021-11-04 00:22:08 +01:00
Claus Vium 4279f13c67 Merge pull request #6766 from Bond-009/minor12 2021-11-04 00:18:34 +01:00
Joe Rogers 4fc0521d69 Move ConvertToRemoteImage to TmdbClientManager 2021-11-03 17:16:40 +01:00
Cody Robibero 3398f7f953 Merge branch 'master' into client-logger 2021-11-03 07:52:06 -06:00
Bond_009 416894008e Minor improvements
* Removed some allocations
* Removed some useless abstractions
2021-11-03 14:02:57 +01:00
Claus Vium 45ceba7ad1 Merge pull request #6764 from zehnerGIT/logspam 2021-11-03 13:02:15 +01:00
Claus Vium 6822693bd6 Merge pull request #6748 from ptalmeida/master 2021-11-03 13:01:53 +01:00
Claus Vium 0187500373 Merge pull request #6761 from Bond-009/startup2 2021-11-03 13:01:37 +01:00
Claus Vium 869d537aaa Merge pull request #6752 from 1337joe/fix-metadata-refresh-deletes-backgrounds 2021-11-03 13:01:23 +01:00
Cody Robibero f444e93a56 Merge remote-tracking branch 'upstream/master' into client-logger 2021-11-02 17:56:12 -06:00
Joe Rogers 149c77d9b1 Remove commented theory data, merge tests 2021-11-02 22:46:53 +01:00
Joe Rogers 2b283d249f Switch to method per image conversion 2021-11-02 21:12:13 +01:00
Claus Vium 2c42d75288 Merge pull request #6027 from fredriklindberg/improve-series-matching 2021-11-02 20:17:49 +01:00
Claus Vium a9c38870f9 Merge pull request #6379 from dearjoey/master 2021-11-02 20:12:42 +01:00
Claus Vium 77634d3b52 Merge pull request #6765 from zehnerGIT/tmdbtags 2021-11-02 20:04:58 +01:00
Claus Vium eec4293e6f Merge pull request #6308 from nielsvanvelzen/openapi 2021-11-02 19:43:57 +01:00
Niels van Velzen 63eeb73608 Update openapi workflow to use .NET 6 2021-11-02 18:58:31 +01:00
Joe Rogers 7fcf01235c Change RemoveImages to array, improve download test 2021-11-02 16:16:06 +01:00
Bond_009 104e36f2f9 Streamline startup code 2021-11-02 16:02:52 +01:00
zehner 3d858955b6 Make tags import from TMDB configurable
new settings added
2021-11-02 15:11:01 +01:00
zehner bbf40d6be2 Update StreamBuilder.cs
LogLevel INFO => DEBUG
2021-11-02 09:35:09 +01:00
Joe Rogers 7da6bd905a Fix edge case in multi-image replacing 2021-11-02 00:31:59 +01:00
Claus Vium 838225e962 Merge pull request #6762 from WWWesten/master
Add missing localization options
2021-11-02 00:06:27 +01:00
Joe Rogers 4a5e8b99a0 Extract duplicate code, add test 2021-11-01 23:17:00 +01:00
Pedro Almeida 5529625025 Translated using Weblate (Portuguese (Portugal))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_PT/
2021-11-01 17:05:34 -04:00
WWWesten 967fd66ca9 Update LocalizationManager.cs 2021-11-02 00:22:16 +05:00
WWWesten a229526454 Update LocalizationManager.cs 2021-11-01 23:47:31 +05:00
WWWesten 0a14279e2a Merge branch 'jellyfin:master' into master 2021-11-01 23:43:29 +05:00
Claus Vium 76eeb8f655 Merge pull request #6745 from crobibero/dlna-profile-save
Use correct id when finding existing dlna profile
2021-11-01 17:19:43 +01:00
Joe Rogers 6881cf6874 Merge remote-tracking branch 'origin/master' into fix-metadata-refresh-deletes-backgrounds 2021-11-01 16:41:05 +01:00
Bond-009 e8efc433fa Merge pull request #6757 from jellyfin/dependabot/nuget/Microsoft.NET.Test.Sdk-17.0.0 2021-11-01 16:19:36 +01:00
Bond-009 98587da53d Merge pull request #6759 from jellyfin/dependabot/nuget/UTF.Unknown-2.5.0 2021-11-01 16:19:01 +01:00
Bond-009 09909d4df2 Merge pull request #6758 from jellyfin/dependabot/nuget/SQLitePCLRaw.bundle_e_sqlite3-2.0.7 2021-11-01 16:17:54 +01:00
dependabot[bot] 2372931b13 Bump UTF.Unknown from 2.4.0 to 2.5.0
Bumps [UTF.Unknown](https://github.com/CharsetDetector/UTF-unknown) from 2.4.0 to 2.5.0.
- [Release notes](https://github.com/CharsetDetector/UTF-unknown/releases)
- [Commits](https://github.com/CharsetDetector/UTF-unknown/compare/v2.4...v2.5)

---
updated-dependencies:
- dependency-name: UTF.Unknown
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-01 12:01:04 +00:00
dependabot[bot] 8e046ce22b Bump SQLitePCLRaw.bundle_e_sqlite3 from 2.0.6 to 2.0.7
Bumps [SQLitePCLRaw.bundle_e_sqlite3](https://github.com/ericsink/SQLitePCL.raw) from 2.0.6 to 2.0.7.
- [Release notes](https://github.com/ericsink/SQLitePCL.raw/releases)
- [Commits](https://github.com/ericsink/SQLitePCL.raw/compare/v2.0.6...v2.0.7)

---
updated-dependencies:
- dependency-name: SQLitePCLRaw.bundle_e_sqlite3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-01 12:00:57 +00:00
dependabot[bot] baafa10e87 Bump Microsoft.NET.Test.Sdk from 16.11.0 to 17.0.0
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.11.0 to 17.0.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Commits](https://github.com/microsoft/vstest/compare/v16.11.0...v17.0.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-01 12:00:53 +00:00
Joe Rogers b478b115e3 Refactor to validate all images up front 2021-11-01 11:53:31 +01:00
Anthony Lavado 022e2d9f97 Merge pull request #6740 from zirdum/master 2021-10-31 21:36:07 -04:00
Joe Rogers 0fbd8d85c8 Validate multi-images, lazy-delete bg on refresh
Fix failing test: Invalid background images not purged by validate
Fixes #6310: Background images only delete when using "Replace existing images" when new image(s) is found to replace them
2021-11-01 00:16:11 +01:00
Joe Rogers 080b02cc4c Add comments, minor cleanup, add tests 2021-10-31 23:56:50 +01:00
Cody Robibero 7b89e0e3a5 Fix tests 2021-10-31 11:06:47 -06:00
Pedro Almeida 10a173c011 Add pt-pt as culture
Makes pt-pt selectable as metadata language
2021-10-31 15:28:51 +00:00
Pedro Almeida a22c57ff33 Fix localization typo with Réunion 2021-10-31 15:13:23 +00:00
Cody Robibero 2f6437a987 Use correct id when finding existing dlna profile 2021-10-31 08:49:24 -06:00
Cody Robibero f4844c08a5 Update Jellyfin.Api/Models/ClientLogDtos/ClientLogDocumentResponseDto.cs
Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>
2021-10-30 11:29:22 -06:00
Cody Robibero 73201ed498 Default log upload to enabled 2021-10-29 06:33:46 -06:00
Cody Robibero bcb1c9b652 Use response dto 2021-10-29 06:33:34 -06:00
Cody Robibero 0e584f6840 Update documentation; use information from authorization; return generated filename 2021-10-28 16:13:37 -06:00
Cody Robibero 91204fc9f0 Fix logfile name if api key is used 2021-10-27 19:40:35 -06:00
Cody Robibero c534c45033 Suggestions from review 2021-10-27 19:20:14 -06:00
zirdum 71ed47a5d3 Update README.md
The link with 'blob' specified in the URL doesn't work for the current version of unRaid. Switching it to 'tree' (which is where the 'blob' link resolves to) works fine.

Changes
Changed link referenced: https://github.com/jellyfin/jellyfin/blob/master/deployment/unraid/docker-templates

To this link: https://github.com/jellyfin/jellyfin/tree/master/deployment/unraid/docker-templates

Issues
Fixes template showing up in unRaid with the changes applied.
2021-10-27 10:54:54 -07:00
zirdum 4885f5e6c9 Update README.md
The link with 'blob' specified in the URL doesn't work for the current version of unRaid. Switching it to 'tree' (which is where the 'blob' link resolves to) works fine.

Changes
Changed link referenced: https://github.com/jellyfin/jellyfin/blob/master/deployment/unraid/docker-templates

To this link: https://github.com/jellyfin/jellyfin/tree/master/deployment/unraid/docker-templates

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

Some files were not shown because too many files have changed in this diff Show More