Compare commits

...

662 Commits

Author SHA1 Message Date
Joshua Boniface
76b4ba3c5e Bump version for 10.4.3 2019-12-06 15:16:22 -05:00
Joshua M. Boniface
292d4b585b Merge pull request #2104 from cvium/avoid_catastrophic_backtracking
Simplify regex to avoid catastrophic backtracking

(cherry picked from commit 6f283d80dc)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-12-06 15:16:09 -05:00
Vasily
0dd08bbbb4 Merge pull request #2071 from excelite/add_default_values_to_logconfig
add filesize limit for logfiles and a maximum logfile count

(cherry picked from commit 8f56baf6d9)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-12-06 15:15:54 -05:00
dkanada
ac8572fd2d Merge pull request #2054 from jellyfin/Bond-009-dlna-getpathvalue
dlna GetPathValue

(cherry picked from commit 5bb6e605fa)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-12-06 15:15:45 -05:00
Joshua Boniface
b300a4e8d4 Bump version for 10.4.2 2019-11-24 15:42:49 -05:00
Joshua Boniface
b3fc995977 Add bad web build branch hotfix
I hate this quick and dirty hack but it makes no sense to port to
master. This fixes a bug whereby we'd build with the master Web branch
on releases due to never checking out the right branch. This is already
obsoleted in the master branch since #1925 already replaces this entire
process for Debuntu builds, and others should be fixed with a more
robust solution. That said, for the 10.4.z release chain, this
ultra-quick solution fixes the problem without changing much.
2019-11-24 15:42:43 -05:00
Joshua Boniface
7f5a070406 Restore MediaBrowser.Model.Extensions after #2008
Removing this caused lines 270-271 to fail as the function override did
not exist in the .NET 2.2 framework. Restores functionality.
2019-11-24 14:59:11 -05:00
Joshua M. Boniface
7ccef6068b Merge pull request #2045 from Bond-009/baseurlfix2
Fix baseurl issues part 2

(cherry picked from commit db581c4d9b)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-11-24 13:43:15 -05:00
dkanada
06aac98996 Merge pull request #2039 from Bond-009/fixcondition
Fix always false condition

(cherry picked from commit 47ad21b6e3)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-11-24 13:42:17 -05:00
Vasily
0f18482ba6 Merge pull request #2034 from Bond-009/easypass
Fix easy password

(cherry picked from commit 13dd63d631)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-11-24 13:41:03 -05:00
Vasily
ffd7835ab5 Merge pull request #2019 from Bond-009/baseurlhotfix
Remove leading / from baseurl

(cherry picked from commit 0836241e90)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-11-24 13:39:49 -05:00
dkanada
fb6b103164 Merge pull request #2008 from Bond-009/pathvalue
Fix GetPathValue function

(cherry picked from commit c87f459ec2)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-11-24 13:39:11 -05:00
Joshua M. Boniface
2de763eef9 Merge pull request #1992 from Bond-009/namingtests
Fix naming tests

(cherry picked from commit 78e0afae2f)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-11-24 13:37:23 -05:00
dkanada
46ab046c34 Merge pull request #1929 from Narfinger/parser-fix4
[Draft][Help wanted] Fix parsing of certain names and adds a default season if no season was found

(cherry picked from commit 61b9b4046a)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-11-24 13:32:23 -05:00
Joshua Boniface
cf54a0e8be Fix missed backport change
This stupid file is back; looks like its location changed in .NET 3.0
versus .NET 2.2. Just revert it back to its original location.

Related to #1859
2019-11-03 14:50:03 -05:00
Joshua Boniface
e73cf46e14 Bump version to 10.4.1 2019-11-03 14:45:56 -05:00
dkanada
39c3b2f044 Merge pull request #1954 from LogicalPhallacy/LogicalPhallacy-patch-NSSM
Use mirror for NSSM

(cherry picked from commit ef623f5129)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-11-03 14:45:20 -05:00
Joshua M. Boniface
7a592a0f15 Merge pull request #1904 from JustAMan/hls-move-2
Switch ffmpeg to hls muxer (from segment) to fix premature stop on non-patched ffmpeg

(cherry picked from commit a460814182)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-11-03 14:45:07 -05:00
Joshua M. Boniface
1fad64cd59 Merge pull request #1903 from anthonylavado/nsis-update
Update NSIS Installer

(cherry picked from commit 9756bdb76e)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-11-03 14:44:57 -05:00
Joshua M. Boniface
86e5dc4607 Merge pull request #1859 from joshuaboniface/copr-fix
Fix COPR build and Fedora packaging

(cherry picked from commit 5d5fa55fe5)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-11-03 14:40:10 -05:00
Bond-009
e98e4766f7 Merge pull request #1933 from cvium/autoreload_log_config
Reload logging.json on changes

(cherry picked from commit da7ba822b0)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-10-24 09:38:42 -04:00
Joshua M. Boniface
6e59671cf6 Merge pull request #1898 from Bond-009/jsonfix
Fix Json serialization error

(cherry picked from commit 91600b1c81)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-10-20 15:17:39 -04:00
Bond-009
86a50367b2 Merge pull request #1909 from KerryRJ/FixDvdsFailingToPlay
Fix System.NullReferenceException when playing Dvds copied to HDD

(cherry picked from commit fdb0c3a1df)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-10-20 14:22:13 -04:00
Bond-009
0212c0b85f Merge pull request #1870 from JustAMan/fix-http-ex1
Fix exception when handling error, log errors better

(cherry picked from commit d8d2e52e3f)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-10-20 14:21:54 -04:00
Vasily
89d365122c Merge pull request #1866 from Bond-009/sqlslow
Change slow query time logging to debug

(cherry picked from commit cadfd5bf3f)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-10-20 14:21:41 -04:00
Vasily
9c0a8350d6 Merge pull request #1863 from joshuaboniface/fix-baseurl-issues
Fix inconsistent BaseUrl behavior

(cherry picked from commit 1176749f14)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-10-20 14:21:26 -04:00
Vasily
818d21718c Merge pull request #1862 from joshuaboniface/bump-version
Fix bump_version for submodule removal

(cherry picked from commit aa9d7d7f04)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-10-20 14:21:06 -04:00
Vasily
0b551c0cd4 Merge pull request #1861 from joshuaboniface/fix-centos-build
Use NVM to install nodejs v8 and yarn for CentOS

(cherry picked from commit 094852ce30)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-10-20 14:20:41 -04:00
Joshua M. Boniface
d8c3b26fa6 Merge pull request #1846 from jellyfin/EraYaN-patch-1
Switch to custom patched build for ffmpeg for the Windows installer
2019-10-06 19:23:01 -04:00
Erwin de Haan
adde41c533 Remove /bin from ffmpeg path. 2019-10-06 22:26:17 +02:00
Erwin de Haan
3925e1dced Fix extracted path from ffmpeg zip. 2019-10-06 22:20:49 +02:00
Erwin de Haan
c7d1206dcb Switch to custom patched build for ffmpeg 2019-10-06 22:03:44 +02:00
Vasily
f1567c64a5 Merge pull request #1844 from dkanada/poster
Fix missing image on seasons without posters
2019-10-06 22:16:21 +03:00
Joshua M. Boniface
d900cc5c53 Merge pull request #1845 from joshuaboniface/mesa-va-drivers-fix
Correct missing mesa-va-drivers package
2019-10-06 15:12:48 -04:00
Poki
5c456231b1 Translated using Weblate (Finnish)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fi/
2019-10-06 09:14:04 -04:00
Joshua Boniface
abde7c0242 Make the comment nicer 2019-10-05 18:38:55 -04:00
Joshua Boniface
762d17c3df Move copy steps further so they're not overwriting 2019-10-05 18:24:38 -04:00
dkanada
e006b7f1e1 add comment explaining a bug fix 2019-10-06 01:24:04 +09:00
dkanada
db10f380d1 fix missing image on seasons without posters 2019-10-06 00:58:27 +09:00
Poki
60f8aa540f Added translation using Weblate (Finnish) 2019-10-05 08:04:16 -04:00
Joshua M. Boniface
7b64c696f7 Merge pull request #1841 from joshuaboniface/docs-url
Update Docs to docs.jellyfin.org URL
2019-10-04 15:13:07 -04:00
Joshua Boniface
0367dc21f8 Update GitHub templates 2019-10-04 15:07:59 -04:00
Joshua Boniface
7a172a9051 Update Docs to docs.jellyfin.org URL 2019-10-04 14:37:36 -04:00
Joshua M. Boniface
5bb44c36e1 Merge pull request #1822 from anthonylavado/readme-fix
Update README for new docs location
2019-10-04 13:24:17 -04:00
Bond-009
3ad34de808 Update ApplicationHost.cs 2019-10-04 18:53:26 +02:00
Joo-Hong Lee
4025b2c6a1 Translated using Weblate (Korean)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ko/
2019-10-04 00:13:59 -04:00
Joshua M. Boniface
6b00cc1a06 Merge pull request #1829 from thornbill/fix-sd-auth
Fix SchedulesDirect authentication
2019-10-02 12:36:25 -04:00
Bill Thornton
80dccdef22 Add using block and HexHelper 2019-10-02 09:51:53 -04:00
SaddFox
b0ec5c527d Translated using Weblate (Slovenian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sl/
2019-10-02 02:13:58 -04:00
Kamilake
5d073713b6 Translated using Weblate (Korean)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ko/
2019-10-02 02:13:58 -04:00
WontTell
d93853375e Translated using Weblate (Spanish (Mexico))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_MX/
2019-10-02 02:13:58 -04:00
h4ss2
26872eb2c2 Translated using Weblate (Arabic)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/
2019-10-02 02:13:58 -04:00
Bill Thornton
119041a425 Fix SchedulesDirect authentication 2019-10-02 01:35:28 -04:00
Joshua M. Boniface
9c85566da7 Merge pull request #1825 from jellyfin/revert-1802-fix-premature-stop
Revert "Fix premature stop when streaming"
2019-10-01 13:55:11 -04:00
Joshua M. Boniface
575b96d03a Revert "Fix premature stop when streaming" 2019-10-01 12:07:09 -04:00
Anthony Lavado
823f648955 Update README for new docs location
Updates several links in the readme to point to our new docs location.
2019-09-30 17:49:37 -04:00
Joshua M. Boniface
7203f463f4 Merge pull request #1820 from joshuaboniface/fix-portable
Properly build DLL for porable
2019-09-30 01:40:50 -04:00
Joshua Boniface
3651755984 Properly build DLL for porable 2019-09-30 01:11:11 -04:00
Joshua M. Boniface
180fb857ed Merge pull request #1819 from nvllsvm/ffmpeg_kill
Ignore exception when attempting to kill ffmpeg that has exited
2019-09-29 20:23:19 -04:00
Joshua M. Boniface
cb09459ad1 Merge pull request #1818 from jellyfin/EraYaN-ci-web-clone-fix
Fix SourceBranch -> SourceBranchName in git clone for Web UI
2019-09-29 19:52:05 -04:00
Andrew Rabert
fd1bdad0e2 Ignore exception when attempting to kill ffmpeg that has exited
A race condition where this code attempts to kill an already exited
ffmpeg process is possible. This results in unnecessary error logging.

This change replaces the generic exception handling with the above
mentioned exception. No log output is produced.
2019-09-29 19:19:53 -04:00
Erwin de Haan
b2ba3a5922 Fix SourceBranch -> SourceBranchName in git clone for Web UI 2019-09-29 23:46:09 +02:00
Joshua M. Boniface
749023bf02 Merge pull request #1811 from joshuaboniface/fix-listen
Configure Kestrel listener to use configured IPs
2019-09-29 17:22:40 -04:00
Joshua M. Boniface
dcc8c7b92a Merge pull request #1808 from joshuaboniface/fix-amf-win
Change Win32 AMF flag to d3d11va
2019-09-29 17:19:23 -04:00
Joshua M. Boniface
f63d591b08 Merge pull request #1817 from joshuaboniface/change-pragma-mode
Change PRAGMA mode from WAL to TRUNCATE
2019-09-29 17:19:08 -04:00
Joshua Boniface
387192610f Handle Kestrel startup failures with a nice error 2019-09-29 17:17:19 -04:00
Joshua M. Boniface
9aec21f6b5 Nicer link format for comment
Co-Authored-By: Bond-009 <bond.009@outlook.com>
2019-09-29 16:18:05 -04:00
Joshua M. Boniface
e183a14933 Merge pull request #1814 from Bond-009/ffmpeg_fix
Fix ffmpeg version check for unknown versions
2019-09-29 16:16:58 -04:00
Joshua Boniface
72edf5b555 Change PRAGMA mode from WAL to TRUNCATE 2019-09-29 15:53:35 -04:00
Joshua Boniface
7fd75bf071 Change Win32 AMF flag to d3d11va for Windows 8+ 2019-09-29 12:39:52 -04:00
Joshua M. Boniface
d64005df40 Merge pull request #1812 from dkanada/settings
Avoid editing preferences without user interaction
2019-09-29 11:43:03 -04:00
Joshua M. Boniface
c523e576c4 Merge pull request #1815 from Bond-009/fix_tests
Fix tests
2019-09-29 11:26:09 -04:00
Bond_009
d1a6e8c99e Fix tests 2019-09-29 13:57:43 +02:00
Bond_009
3e1aab6b29 Fix ffmpeg version check for unknown versions 2019-09-29 13:43:47 +02:00
dkanada
7a88e7fa34 avoid editing preferences without user interaction 2019-09-29 16:21:32 +09:00
Joshua Boniface
cabb9aed31 Configure Kestrel listener to use configured IPs 2019-09-29 00:37:48 -04:00
Joshua M. Boniface
61f2c41b76 Recursively zip Windows packages (#1809)
Without -r the directory is not properly zipped up.
2019-09-29 00:33:31 -04:00
Joshua M. Boniface
3249fbb715 Merge pull request #1804 from Bond-009/ffmpeg_tests
Add tests for EncoderValidator and add support for ffmpeg 4.2
2019-09-28 18:08:38 -04:00
Joshua M. Boniface
2d797adc08 Merge pull request #1773 from sparky8251/remove-wan-ddns
Remove WAN DDNS
2019-09-28 18:08:24 -04:00
Joshua M. Boniface
c069496b27 Merge pull request #1770 from sparky8251/remove-wan-ip
Remove WAN IP Detection
2019-09-28 18:08:10 -04:00
Joshua M. Boniface
c1e8087b11 Merge pull request #1803 from Bond-009/revision
Don't log revision number
2019-09-28 17:16:38 -04:00
Joshua M. Boniface
427d52cf9a Merge pull request #1805 from Bond-009/plugin_fix
Fix plugin installation
2019-09-28 15:23:08 -04:00
Bond_009
06d420f743 Fix plugin installation 2019-09-28 21:06:58 +02:00
Bond_009
05a1510b31 Add more test data 2019-09-28 19:49:41 +02:00
Bond_009
1b01a6ece1 Add tests for EncoderValidator
* Add support for ffmpeg 4.2
* Parse the complete ffmpeg version instead of only the first 2 digits
* Make max and min version optional
* Remove max limitation (for now)
* Style improvements
2019-09-28 19:41:34 +02:00
Joshua M. Boniface
3577ef6814 Merge pull request #1802 from JustAMan/fix-premature-stop
Fix premature stop when streaming
2019-09-28 11:15:33 -04:00
Joshua M. Boniface
75b7c9ac36 Do explicit dotnet publish in Dockerfiles (#1801)
The common.build.sh script was removed in #1793 but the Dockerfiles
still used this to perform the dotnet publish. Remove that call and do
the publish explicitly.
2019-09-27 23:02:18 -04:00
Bond_009
4f63bfd616 Don't log revision number 2019-09-27 23:58:04 +02:00
Vasily
4fae733eef Cleaned up move to HLS muxer 2019-09-27 18:22:43 +03:00
Vasily
36a34f911e Replaced "stream" muxer with "hls" trying to fix "premature stop" issue 2019-09-27 16:37:41 +03:00
Joshua M. Boniface
e4d5e5bf91 Merge pull request #1793 from joshuaboniface/fix-build
Clean up and update builds to use new jellyfin-web
2019-09-26 22:24:41 -04:00
Joshua Boniface
547a6121b0 Remove redundant yarn build command 2019-09-26 22:09:19 -04:00
Anthony Lavado
bae5e3795e Fix SetImage to avoid out of range exception (#1798)
* Fix SetImage to avoid out of range exception

* Actually use the new images we've retrieved
2019-09-26 09:47:48 -04:00
Joshua Boniface
3b935d8fd0 Clean up old changelog entires from Debian package 2019-09-25 14:54:39 -04:00
Joshua Boniface
15b83f8b55 Clean up and fix Fedora/CentOS builds
This performs a lot of bugfixing and general cleanup to the
Fedora/CentOS builds, including moving the create_tarball into the
docker-build.sh script, remove some old long versions from the spec
file, correcting several bugs with the Docker environment including
splitting them into more discrete layers, and finally making sure
jellyfin-web is included properly in the RPM.
2019-09-25 14:45:15 -04:00
Joshua Boniface
56a879e148 Use redirection instead of tee 2019-09-25 14:20:49 -04:00
Vasily
fc99f1f563 Merge pull request #1785 from dkanada/compat
Add mediabrowser route back for now
2019-09-25 14:18:21 +03:00
Joshua Boniface
4b257b7b4a Clean up web_build_dir in docker-build.sh 2019-09-25 01:36:20 -04:00
Joshua Boniface
172a81b22c Clean up deployment directory
1. Update README.md to remove some old info
2. Remove common.build.sh from all the build scripts
3. Remove common.build.sh script
4. Remove the docker folder as this isn't used at all
2019-09-25 01:28:07 -04:00
Joshua Boniface
5c7ca6b363 Port Windows x86 build to Docker and add web build 2019-09-25 01:27:35 -04:00
Joshua Boniface
93b213b59f Port Windows x64 build to Docker and add web build 2019-09-25 01:27:28 -04:00
Joshua Boniface
3b669521da Port Portable build to Docker and add web build 2019-09-25 01:10:04 -04:00
Joshua Boniface
05f01b2c45 Port MacOS build to Docker and add web build 2019-09-25 01:06:50 -04:00
Joshua Boniface
f36b898a4d Port Linux build to Docker and add web build 2019-09-25 01:06:18 -04:00
Joshua Boniface
fa9b0d9da1 Add web build to Fedora package build 2019-09-24 23:25:46 -04:00
Joshua Boniface
1c2fd4ef84 Add web build to CentOS package build 2019-09-24 23:24:22 -04:00
Joshua Boniface
be3b05df68 Add web build to Ubuntu arm64 package build 2019-09-24 23:17:11 -04:00
Joshua Boniface
601a50e430 Add web build to Ubuntu armhf package build 2019-09-24 23:15:31 -04:00
Joshua Boniface
03d60438e2 Add web build to Ubuntu amd64 package build 2019-09-24 23:13:40 -04:00
Joshua Boniface
9b6720ce80 Add web build to Debian arm64 package build 2019-09-24 23:11:18 -04:00
Joshua Boniface
b9e0a0b1ac Add web build to Debian armhf package build 2019-09-24 23:09:47 -04:00
Joshua Boniface
d22fd964c2 Add web build to Debian amd64 package build 2019-09-24 23:06:47 -04:00
Joshua Boniface
188ad540ee Remove submodule handling from build script 2019-09-24 23:06:27 -04:00
Joshua Boniface
12f24674fb Fix up Windows build script copy 2019-09-24 22:10:12 -04:00
Vasily
ac9dfa8e93 Merge pull request #1775 from Bond-009/fixes
Fix multiple mistakes and warnings
2019-09-24 19:08:39 +03:00
dkanada
b5b7db1f32 add an exception to logging 2019-09-24 23:59:24 +09:00
dkanada
ab7e697f30 add mediabrowser route back for now 2019-09-24 23:59:24 +09:00
Joshua M. Boniface
b086f6d330 Merge pull request #1791 from EraYaN/ci-web-build
Remove submodule and add clone and build to CI for web UI
2019-09-24 10:48:09 -04:00
Erwin de Haan
a73d87229a Add extra conditions. 2019-09-24 16:38:51 +02:00
Erwin de Haan
e9fb46b0cd Fix path typo and add same code to main build. 2019-09-24 16:32:57 +02:00
Erwin de Haan
0ca0d9d01e Remove submodule and add clone and build to CI 2019-09-24 16:22:26 +02:00
dkanada
1156b8f100 Merge pull request #1788 from joshuaboniface/ssl-dep-ubu
Include libssl-dev dep in Ubuntu build containers
2019-09-24 13:12:03 +09:00
Bond_009
c9820d30ed Fix multiple mistakes and warnings 2019-09-23 20:32:44 +02:00
Dan Johansen
b8fd6a7ec3 Translated using Weblate (Danish)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/da/
2019-09-23 12:28:24 -04:00
WWWesten
2c2a55abab Translated using Weblate (Russian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ru/
2019-09-22 23:13:44 -04:00
Mário Victor Ribeiro Silva
f7e9b0a27f Translated using Weblate (Portuguese (Brazil))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/
2019-09-22 23:13:44 -04:00
pucherot
6b33089274 Translated using Weblate (Spanish)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es/
2019-09-22 23:13:44 -04:00
Anthony Lavado
229bd598b5 Add escapes for path quotes in the NSIS Installer (#1777)
This adds backslashes to escape the `--datadir` path in the Windows Installer. Without this, the path would be dropped at the first space (e.g. `C:\Test Area\Jellyfin` would become `C:\Test`).

Fixes #1773.
2019-09-20 13:45:20 -04:00
Joshua Boniface
260dd37bd5 Include libssl-dev dep in Ubuntu build containers 2019-09-20 11:55:51 -04:00
Mark Bai
54d33c06c7 Translated using Weblate (Chinese (Simplified))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/
2019-09-20 03:13:39 -04:00
Vasily
a7358171cf Merge pull request #1772 from sparky8251/better-hidden-defaults
Hide new users from public API by default
2019-09-19 23:28:53 +03:00
sparky8251
14f563d7c2 Removed WAN DDNS
It's odd that JF still had code lying around for generating a self signed cert. Currently, it does not do this so this code has been removed.

JF also appears to have functions in place to modify provided certs? Warrants deeper investigation. JF should not be attempting modifications of any certs under any circumstance.
2019-09-19 15:56:54 -04:00
sparky8251
44a3e0a97b Hide new users from public API by default 2019-09-19 15:16:57 -04:00
Joshua M. Boniface
e19474d22f Merge pull request #1643 from Bond-009/docs3
Fix some documentation warnings for MediaBrowser.XbmcMetadata
2019-09-19 11:26:26 -04:00
sparky8251
208c8b2b9d Remove missed unused functions
Forgot to remove these in the previous WAN IP detection removal commit
2019-09-19 11:21:19 -04:00
sparky8251
0562b4cf6f Remove WAN IP Detection 2019-09-19 10:55:58 -04:00
Joshua M. Boniface
8ba86fe272 Merge pull request #1765 from EraYaN/ci-add-submodule-update
Add submodule update to CI build.
2019-09-18 14:55:19 -04:00
Bond-009
fad4594062 Merge pull request #1768 from whooo/filewrite-log2
Set log level to debug for HTTP range requests
2019-09-18 20:22:44 +02:00
Erik Larsson
74864832ca Set log level to debug for HTTP range requests
This removes some spam when a DLNA renderer uses byte seeking.
2019-09-18 18:29:15 +02:00
Erwin de Haan
9c95eba5a1 Add VSTest support to CI (#1696)
* Add VSTest support to CI

Add result publishing

Move tests to windows.

Spacing issue.

Image name update.

* Passthrough main debug build

* Rectify mistake and build test assemblies seperately.

* Remove Test dependency
2019-09-18 11:43:02 -04:00
Bond-009
6f17a0b7af Remove legacy auth code (#1677)
* Remove legacy auth code

* Adds tests so we don't break PasswordHash (again)
* Clean up interfaces
* Remove duplicate code

* Use auto properties

* static using

* Don't use 'this'

* Fix build
2019-09-17 12:07:15 -04:00
Erwin de Haan
f8fed49225 Another condition update. 2019-09-17 16:45:18 +02:00
Erwin de Haan
8b438b68cc Added Better submodule updates. 2019-09-17 16:40:11 +02:00
Erwin de Haan
8f2ec3b197 Add submodule update to build. 2019-09-17 16:23:27 +02:00
Joshua M. Boniface
adc2a68a98 Merge pull request #1744 from Bond-009/dataprovider
Rewrite `ItemDataProvider` to be more robust
2019-09-17 09:11:50 -04:00
Joshua M. Boniface
39faadc9dc Merge pull request #1751 from Bond-009/login
Reset invalid login counter on successfull login
2019-09-17 09:11:25 -04:00
Joshua M. Boniface
c4c7ced948 Merge pull request #1764 from EraYaN/publish-ci-fault
Fix publish CI YAML parse error
2019-09-16 21:49:01 -04:00
Erwin de Haan
6fa1c5e214 Fix release symlink name. 2019-09-17 01:41:14 +02:00
Erwin de Haan
831ce4da13 Skip checkout for Publish CI pipelines 2019-09-17 01:37:42 +02:00
Erwin de Haan
24a5bebabe Fix yaml parse error 2019-09-17 01:33:17 +02:00
Erwin de Haan
42f761582f Add two manually triggered pipelines for publishing artifacts. (#1763)
* Added two extra pipelines.

* Change the sshEndpoint
2019-09-16 19:23:52 -04:00
nevado
cb32bf1c4f Add mesa-va-drivers to main Dockerfile (#1727) 2019-09-15 00:31:13 -04:00
Bond-009
318b9949f2 Improve Skia error handling (#1752) 2019-09-15 00:27:42 -04:00
Bond-009
221b831bb2 Reset invalid login counter on successfull login 2019-09-13 17:18:45 +02:00
Andrew Rabert
cc8609d0aa Merge pull request #1746 from nvllsvm/yarn
Docker - Build jellyfin-web
2019-09-12 22:19:35 -04:00
Andrew Rabert
03f32978c0 Docker - Build jellyfin-web 2019-09-12 18:24:09 -04:00
Bond_009
8fe7b6551f Rewrite ItemDataProvider to be more robust
* Stop locking 2+ times per operation
* Don't clone the list multiple times
* Keep the lock for the duration of the operation
2019-09-12 21:34:55 +02:00
Bond-009
2919cf28ea Update deps (#1735) 2019-09-11 13:31:35 -04:00
Abdulkadir Furkan Şanlı
e131078673 Translated using Weblate (Turkish)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/tr/
2019-09-11 10:13:20 -04:00
qqq-qqqq
e1b445d133 Translated using Weblate (Chinese (Simplified))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/
2019-09-11 10:13:20 -04:00
tluciomiranda
177ca3ccba Translated using Weblate (Portuguese (Portugal))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_PT/
2019-09-11 10:13:20 -04:00
Joshua M. Boniface
c63a53959c Merge pull request #1731 from EraYaN/segment-time-delta-culture-fix
Fix culture problem on Windows for segment_time_delta argument.
2019-09-10 10:41:59 -04:00
Erwin de Haan
e8b13ea8a9 Fix culture problem on Windows for segment_time_delta argument. 2019-09-10 16:31:35 +02:00
Erwin de Haan
2f2010ce59 NSIS improvements (#1692)
* Much better, but still broken

It crashes with two custom pages after one another. (So when the service should be installed).

* Fixed the problems and finished the NSIS installer.

Also ignored some of the artifacts.

* Added changes to CI for setup building.

Consolidate building and fixed git error.

Small CI fixes.

Move UX repo to SourcesDirectory

Fix stupid checkout <> clone error.

Fix typo in PowerShell command.

Artifact publish tasks can not have wildcards.
2019-09-09 17:40:51 -04:00
Anthony Lavado
e6a1407786 Merge pull request #1723 from Bond-009/mrmc
Possible fix for MrMC
2019-09-08 18:10:47 -04:00
Bond_009
3eca8b9c98 Address comments 2019-09-08 21:19:41 +02:00
Bond_009
0803a916aa Fix some documentation warnings for MediaBrowser.XbmcMetadata 2019-09-08 21:19:41 +02:00
Bond_009
675754bc5c Possible fix for MrMC 2019-09-08 21:07:29 +02:00
Joshua M. Boniface
2638759b42 Merge pull request #1708 from crobibero/patch-1
Fix translate link
2019-09-03 00:03:05 -04:00
Joshua M. Boniface
b4d722b9f2 Merge pull request #1709 from joshuaboniface/fix-rootdir-crash
Fix crash due to missing userRootFolder data
2019-09-03 00:02:39 -04:00
Joshua Boniface
baa30b41de Add debug logs and try/catch when creating folder 2019-09-02 23:32:03 -04:00
Cody Robibero
299193e2bd Fix translate link 2019-09-02 20:06:22 -06:00
Joshua M. Boniface
fde9dd2a61 Merge pull request #1693 from joshuaboniface/update-libssl
Update Debian build to Buster and LibSSL 1.1
2019-09-02 21:11:57 -04:00
Joshua Boniface
5552e8cbd7 Add missing build dependency 2019-09-02 20:13:28 -04:00
Joshua M. Boniface
2aecc3fa1b Merge pull request #1699 from joshuaboniface/bump-version
Bump version to 10.4.0
2019-09-02 20:03:31 -04:00
Anthony Lavado
e2577ea1c7 Merge pull request #1707 from nvllsvm/default_build_arg
Fix default build arg
2019-09-02 15:45:52 -04:00
Andrew Rabert
11346c000e Fix default build arg 2019-09-02 15:13:08 -04:00
Bond-009
ee637e8fec Fix warnings, improve performance (#1665)
* Fix warnings, improve performance

`QueryResult.Items` is now a `IReadOnlyList` so we don't need to
allocate a new `Array` when we have a `List` (and `Items` shouldn't need to
be mutable anyway)

* Update Providers .csproj to latest C#

* Remove extra newline from DtoService.cs

* Remove extra newline from UserLibraryService.cs
2019-09-02 02:19:29 -04:00
Anthony Lavado
cb393c215a Merge pull request #1686 from Bond-009/warn7
More warning fixes
2019-09-02 02:07:19 -04:00
Anthony Lavado
c4eac8b3c6 Merge pull request #1702 from Bond-009/warn8
Fix more warnings
2019-09-02 02:05:20 -04:00
Anthony Lavado
852e5e29ca Merge pull request #1704 from nvllsvm/fix_replace_multiple_spaces
Fix replace multiple spaces
2019-09-02 01:33:14 -04:00
Andrew Rabert
907b3185c2 Use regex to replace multiple spaces with one space
This communicates the intent of the code more clearly than before.
No noticeable performance impact.
2019-09-01 21:44:34 -04:00
Andrew Rabert
6478cd2ea4 Fix infinite looping when scanning TV metadata
It's a possible that name and sb will never be equal. This is caused by
additional replacements before this loop.
Regression introduced in c699c546e4
2019-09-01 21:40:47 -04:00
Bond_009
1616f24cee Fix more warnings 2019-09-01 18:39:23 +02:00
Bond-009
160718efe2 Merge pull request #1691 from sammyrc34/vaapifix
Enable VAAPI decoding without hardware encoding
2019-09-01 17:30:58 +02:00
Bond-009
a266b54ad6 Merge pull request #1683 from dkanada/misc
Move the transcode path and other small fixes
2019-09-01 17:27:08 +02:00
Anthony Lavado
fde024e7b8 Merge pull request #1700 from thornbill/flac
Restore flac mime type
2019-09-01 02:05:15 -04:00
Bill Thornton
3a600687ea Restore flac mime type 2019-09-01 01:15:34 -04:00
Anthony Lavado
67f38006f8 Merge pull request #1680 from marius-luca-87/q6fn_dlna_seek
Fix q6fn dlna seek for direct play
2019-09-01 00:48:30 -04:00
Samantha Collard
627bde4b72 Fix VAAPI 8-bit HW to SW transcoding for some media 2019-09-01 13:20:18 +10:00
Joshua Boniface
742102b541 Bump version to 10.4.0 2019-08-31 21:39:10 -04:00
Joshua Boniface
5251a5ca79 Allow selecting web branch and fix Docker sed 2019-08-31 21:38:26 -04:00
Joshua M. Boniface
ba06ef57a9 Merge pull request #1687 from Bond-009/isomounter
Move IsoMounter to plugin
2019-08-31 21:22:41 -04:00
Joshua M. Boniface
0d7adc3382 Merge pull request #1695 from nvllsvm/dfa
Make Docker ffmpeg version configurable
2019-08-31 21:12:37 -04:00
Andrew Rabert
1c4755f26a docker - make ffmpeg version configurable 2019-08-31 13:26:13 -04:00
Joshua Boniface
93a668de8b Add libssl-dev dependency 2019-08-31 01:17:23 -04:00
Joshua Boniface
1d5b11f7f6 Update GCC crossbuild to version 8 2019-08-31 01:12:11 -04:00
Joshua Boniface
b1c7b88b5b Use latest 2.2.6 dotnet SDK image (2.2.401) 2019-08-31 00:39:46 -04:00
Joshua Boniface
bedc2be525 Add libssl-dev build dependency 2019-08-31 00:32:26 -04:00
Samantha Collard
a321ca5b39 Enable VAAPI decoding without hardware encoding
Enable VAAPI command arguments to ffmpeg if VAAPI is selected, and
add the "hwdownload" filter if transcoding from VAAPI to software.
Also support transforming 10 bit colourspace to 8-bit, consistent
with other hardware encoding options, at least until client pixel
formats are configurable.
2019-08-31 12:04:31 +10:00
Bond_009
14fbd845c2 Move IsoMounter to plugin 2019-08-29 23:11:55 +02:00
Bond_009
e4f893a0eb More warning fixes 2019-08-29 22:28:33 +02:00
Anthony Lavado
a30876c3ff Merge pull request #1685 from Bond-009/format
Fix invalid arg for
2019-08-29 15:55:31 -04:00
Bond_009
0aaaaab7a0 Fix invalid arg for 2019-08-29 20:25:56 +02:00
dkanada
21ff63c371 move the transcode path 2019-08-29 00:14:50 -07:00
marius
3deeca43a1 - use streamInfo.StartPositionTicks if provided over userdata.PlaybackPositionTicks when creating the Samsung Bookmark Info 2019-08-29 02:38:53 +03:00
Bond-009
503ab56a59 Merge pull request #1678 from marius-luca-87/dlna_seek_exception_fix
Fix ArgumentOutOfRangeException in ParseTimeSeekHeader
2019-08-28 20:13:58 +02:00
marius
b711ece829 - fix ArgumentOutOfRangeException at MediaBrowser.Api.Playback.BaseStreamingService.ParseTimeSeekHeader (second substring argument is length) 2019-08-28 21:05:17 +03:00
Anthony Lavado
efaa668158 Merge pull request #1633 from Bond-009/udpclient
Attempt to fix #1391
2019-08-28 09:41:11 -04:00
Anthony Lavado
a2fd82137c Merge pull request #1676 from Bond-009/login
Fix login
2019-08-28 09:38:12 -04:00
Bond_009
efc4805233 Fix login 2019-08-28 14:45:46 +02:00
Bond_009
dc194015c2 Remove unused args 2019-08-28 13:59:17 +02:00
Bond_009
5dd332b63d Attempt to fix #1391 2019-08-28 13:55:36 +02:00
Anthony Lavado
874f02631b Merge pull request #1641 from cvium/tmdb_cleanup
Tmdb cleanup and re-add Writer and Producer
2019-08-27 23:29:01 -04:00
Andrew Rabert
24775f4988 Merge pull request #1651 from sl1288/master
Fix local trailers playback
2019-08-27 19:02:05 -04:00
Odd Stråbø
f255788383 Translated using Weblate (Norwegian Bokmål)
Currently translated at 95.7% (90 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nb_NO/
2019-08-26 22:06:50 -04:00
Pafzedog
ba0997a8db Translated using Weblate (French)
Currently translated at 95.7% (90 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/
2019-08-26 22:06:50 -04:00
Axel Gabriel Calle Granda
7d4bb28d18 Translated using Weblate (Spanish)
Currently translated at 95.7% (90 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es/
2019-08-26 22:06:50 -04:00
Matzi24GR
d2c69e7733 Translated using Weblate (Greek)
Currently translated at 95.7% (90 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/el/
2019-08-26 22:06:50 -04:00
Βασίλης Μουρατίδης
143a408342 Translated using Weblate (Greek)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/el/
2019-08-26 22:06:50 -04:00
Matzi24GR
6be68a3656 Translated using Weblate (Greek)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/el/
2019-08-26 22:06:50 -04:00
Anthony Lavado
b744ebb3b3 Merge pull request #1656 from loli10K/fix_firewalld_service
Fix firewall-cmd: Error: INVALID_SERVICE: 'jellyfin' not among existing services
2019-08-26 17:02:05 -04:00
Joshua Boniface
fb37f4a1d5 Use base Debian image in Docker build 2019-08-26 11:05:15 -04:00
Joshua Boniface
5945a638ff Update builds to Debian 10 and OpenSSL to 1.1-only 2019-08-26 11:00:36 -04:00
Bond-009
e87d7cfaf3 Merge pull request #1659 from jellyfin/fix_ffmpeg
Fix segment_time_delta value for ffmpeg 4.1
2019-08-25 13:38:11 +02:00
Claus Vium
2e66361482 Move argument to new line 2019-08-24 11:38:33 +02:00
Claus Vium
15b054be94 Fix style issues 2019-08-24 11:17:17 +02:00
Joshua M. Boniface
1dfd5000ff Merge pull request #1499 from crobibero/log-password-ip
Log IP address on failed login attempt
2019-08-23 23:40:45 -04:00
crobibero
4f974122f8 log password on failed login attempt 2019-08-23 23:28:52 -04:00
Joshua M. Boniface
dc1782d049 Merge pull request #1646 from Bond-009/lock
Return DB lock immediately
2019-08-21 18:24:47 -04:00
loli10K
588db95e2a Fix firewall-cmd: Error: INVALID_SERVICE: 'jellyfin' not among existing services 2019-08-20 20:27:31 +02:00
Joshua M. Boniface
1bce9a89b6 Merge pull request #1433 from fhriley/h265
Add support for encoding with libx265 and hevc_nvenc
2019-08-19 16:28:43 -04:00
Joshua M. Boniface
d95c04787c Merge branch 'master' into h265 2019-08-19 14:57:48 -04:00
Joshua M. Boniface
d99278da1d Merge pull request #1650 from Bond-009/sqlitepclv2
Upgrade SQLitePCL to v2
2019-08-19 14:43:54 -04:00
SL1288
fdc24ec2ee Fix LocalTrailers playback. 2019-08-19 18:56:41 +02:00
Bond_009
3fd489d1cb Upgrade SQLitePCL to v2 2019-08-19 17:03:21 +02:00
Claus Vium
058e077422 Add newline after end tag 2019-08-19 12:13:34 +02:00
Claus Vium
d2b8672c1c Require latest C# version in Providers project 2019-08-19 12:12:00 +02:00
dkanada
c8474f734c Merge pull request #1644 from Bond-009/hiddenwarn
Fix possible hidden exceptions
2019-08-19 01:59:07 -07:00
dkanada
5626709de5 Merge pull request #1645 from Bond-009/fixusers
Fix UserNotFoundError
2019-08-19 01:58:24 -07:00
Bond_009
f70a63d575 Return DB asap 2019-08-18 22:05:06 +02:00
Bond_009
24fac4b191 Fix UserNotFoundError 2019-08-18 20:12:25 +02:00
Bond_009
99aea27723 Fix possible hidden exceptions
If an error occurred while starting the server which in turn caused an
exception in the dispose method of the apphost, the first exception
wouldn't get logged.
2019-08-18 20:01:08 +02:00
Joshua M. Boniface
94e25e898a Merge pull request #1511 from crankdoofus/master
Add NSIS installer build support
2019-08-18 13:56:20 -04:00
Joshua M. Boniface
4bb0c2d053 Merge pull request #1642 from cvium/fix_slow_db
Speed up BaseItem deserialization
2019-08-18 13:42:08 -04:00
Claus Vium
f48eaccc51 Use reader.GetString instead of indexing 2019-08-18 17:32:41 +02:00
Claus Vium
e7c05dcfaf Speed up BaseItem deserialization 2019-08-18 17:22:45 +02:00
Claus Vium
82b0015b30 Fix style issues 2019-08-18 14:50:26 +02:00
Claus Vium
78441730a7 Add Writer and Producer to crew list 2019-08-18 14:50:26 +02:00
Claus Vium
5ea1299030 Fix api url 2019-08-18 14:50:26 +02:00
Claus Vium
817d9b3389 Move and rename tmdb providers for better separation 2019-08-18 14:50:26 +02:00
dkanada
25a590e8cd Merge pull request #1636 from Bond-009/isomounter
Add analysers to Emby.IsoMounting and enable TreatWarningsAsErrors
2019-08-18 03:19:27 -07:00
Anthony Lavado
6766e04dd6 Merge pull request #1521 from Bond-009/hdhomerun
Clean up livestreaming code
2019-08-17 02:24:39 -04:00
Anthony Lavado
28d707604b Merge pull request #1629 from cvium/fix_tvdb_guest_stars
Fix tvdb guest stars with multiple roles
2019-08-17 02:22:07 -04:00
Anthony Lavado
f1f4b1a184 Merge pull request #1628 from cvium/fix_tvdb_ep_provider_id
Always fetch episode id as EpisodeInfo does not contain it
2019-08-17 02:10:36 -04:00
Anthony Lavado
f41a608f11 Merge pull request #1452 from Bond-009/usermanager2
Improvements to UserManager
2019-08-17 02:02:55 -04:00
Bond_009
1bc9b42c57 More fixes 2019-08-16 21:18:37 +02:00
Bond-009
4b37caa63a Update SharedHttpStream.cs 2019-08-16 21:13:18 +02:00
Bond_009
237db8ae92 Clean up livestreaming code 2019-08-16 21:13:18 +02:00
Claus Vium
9e3f4ac954 Move the first argument to its own line 2019-08-16 21:10:42 +02:00
Bond_009
8d3b5c851d Improvements to UserManager 2019-08-16 21:06:11 +02:00
Bond_009
dc662beefe Add analysers to Emby.IsoMounting and enable TreatWarningsAsErrors 2019-08-16 21:03:45 +02:00
Anthony Lavado
7a27dd8a1b Merge pull request #1632 from Bond-009/locale
Improve LocalizationManager
2019-08-16 14:17:01 -04:00
Anthony Lavado
af3c4e0ce8 Merge pull request #1588 from dkanada/url
Add base url option to server configuration
2019-08-16 14:16:18 -04:00
Claus Vium
e4158d9703 Continue 2019-08-16 20:11:01 +02:00
Claus Vium
8d230e67a2 Place args on separate lines 2019-08-16 20:09:30 +02:00
Claus Vium
daf29233e6 Invert the second if 2019-08-16 20:07:00 +02:00
Claus Vium
15f7a2078b Invert the if 2019-08-16 19:58:44 +02:00
Claus Vium
26b4fb21fe Update MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
Co-Authored-By: Bond-009 <bond.009@outlook.com>
2019-08-16 19:53:28 +02:00
Claus Vium
617f7e8b5b Fix segment_time_delta value for ffmpeg 4.1 2019-08-16 19:52:54 +02:00
Anthony Lavado
be5a819621 Merge pull request #1634 from Bond-009/fixbuild
Fix build on .Net Core 2.x
2019-08-16 13:49:02 -04:00
Bond_009
c0e71cdea7 Fix build on .Net Core 2.x 2019-08-16 19:05:30 +02:00
Anthony Lavado
b89c26ab57 Merge pull request #1631 from Bond-009/fixbuild
Fix build for .Net Core 2.x
2019-08-16 12:03:27 -04:00
Bond_009
499c3dbdca Fix build for .Net Core 2.x 2019-08-16 17:37:40 +02:00
Bond_009
c699c546e4 Improve LocalizationManager 2019-08-16 17:31:47 +02:00
dkanada
bb04545068 Merge pull request #1614 from Bond-009/docs2
Document all public/internal members of Emby.Drawing
2019-08-15 21:01:44 -07:00
Claus Vium
11504321b5 Handle negative roleStartIndex since not all guest stars have roles 2019-08-15 19:54:01 +02:00
Claus Vium
f7f3627bb1 Remove unused import 2019-08-15 14:56:49 +02:00
Claus Vium
f4a99beb16 Fix tvdb guest stars loop 2019-08-15 14:54:22 +02:00
Claus Vium
38b0967044 Log episode id and series id when either request fails 2019-08-15 13:43:12 +02:00
Claus Vium
14575f0a06 Always fetch episode id as EpisodeInfo does not contain it 2019-08-15 13:39:56 +02:00
dkanada
685e9e4f58 Merge pull request #1584 from Bond-009/checksum
Check checksum for plugin downloads
2019-08-15 01:00:33 -07:00
dkanada
535e0d2553 Merge pull request #1625 from Bond-009/crash
Fix instant crash.
2019-08-14 15:05:35 -07:00
Bond_009
d62a3f0e57 Fix master 2019-08-15 00:00:21 +02:00
dkanada
ca12763adc Merge pull request #1624 from Bond-009/nullref
Fix possible Nullref
2019-08-14 12:42:58 -07:00
Bond_009
2fdf7f1098 Properly dispose DisplayPreferencesRepository 2019-08-14 20:35:36 +02:00
Bond_009
e5b163b86a Fix possible nullref 2019-08-14 20:24:44 +02:00
Anthony Lavado
f8202384a6 Merge pull request #1622 from anthonylavado/transcoding-cleanup
Add a task to clean up transcode cache
2019-08-14 12:25:05 -04:00
Anthony Lavado
35da4ffa3e Remove unneeded comment 2019-08-14 11:59:14 -04:00
Anthony Lavado
4762e2fc6c Add a task to clean up transcode cache 2019-08-14 01:51:46 -04:00
dkanada
8f8d8e3d0b Merge pull request #1581 from Bond-009/socket1
Use System.Net abstractions instead of raw socket
2019-08-13 19:58:57 -07:00
Anthony Lavado
29623d36e8 Merge pull request #1568 from whooo/master
Add DLNA headers if requested by the client
2019-08-13 01:29:16 -04:00
dkanada
443ccbf426 Merge pull request #1613 from Bond-009/docs
Update deps + document startup project
2019-08-12 18:42:30 -07:00
Bond_009
838e5d05d5 Document all public/internal members of Emby.Drawing
Forces all new public/internal members to be documented.
Enables TreatWarningsAsErrors for Emby.Drawing
2019-08-11 16:52:37 +02:00
Bond_009
7243689215 Minor improvements 2019-08-11 15:57:36 +02:00
Bond_009
5eaf5465a5 Check checksum for plugin downloads
* Compare the MD5 checksum when downloading plugins
* Reduced log spam due to http requests
* Removed 'GetTempFileResponse' function from HttpClientManager
* Fixed caching for HttpClientManager
2019-08-11 15:54:58 +02:00
Bond_009
cb492fe3c7 Improve clickable link 2019-08-11 15:17:39 +02:00
Bond_009
003238ef5e Update deps + document startup project
* Fixed the release build
* Documented all public/internal members of Jellyfin.Server
* Enable TreatWarningsAsErrors for debug builds for Jellyfin.Server

This will ensure that any new public/internal members of Jellyfin.Server
are documented
2019-08-11 15:11:53 +02:00
dkanada
1ad67e223f Merge pull request #1462 from Bond-009/installationmanager
Improvements to InstallationManager
2019-08-11 03:47:10 -07:00
whooo
9556561a77 Merge branch 'master' into master 2019-08-11 12:42:19 +02:00
dkanada
97d6c2db6b keep old base url for now 2019-08-11 00:35:18 -07:00
dkanada
d521e5c36a add base url to server configuration 2019-08-11 00:35:18 -07:00
dkanada
c987203f5a remove old routes from http server 2019-08-11 00:33:13 -07:00
Joshua M. Boniface
a96fa7a5c7 Merge pull request #1397 from Bond-009/passfast
Streamline authentication proccess
2019-08-10 21:42:46 -04:00
dkanada
5c366e4697 Merge pull request #1612 from Bond-009/warn4
Fix warnings
2019-08-10 15:12:54 -07:00
dkanada
4f592e9c33 Merge pull request #1484 from SenorSmartyPants/DVD-Order
Update TVDB provider to search based on series display order
2019-08-09 23:26:42 -07:00
dkanada
b5f3f28f41 Merge pull request #1578 from Bond-009/httpresponse
Replace custom code with Asp.Net Core code
2019-08-09 23:26:10 -07:00
Joshua M. Boniface
f8ad6655fb Merge pull request #1023 from Bond-009/cultinvar
Use CultureInvariant string conversion for Guids
2019-08-09 22:33:18 -04:00
Bond_009
25917db07a Fix doc releated warnings 2019-08-09 23:50:40 +02:00
Bond_009
9b2cf8501f Add last one 2019-08-09 23:24:04 +02:00
Bond_009
52c1b45feb Fix build 2019-08-09 23:17:54 +02:00
Bond-009
6032f31aa6 Use CultureInvariant string conversion for Guids 2019-08-09 23:17:54 +02:00
Bond_009
2a58c643d2 Fix more warnings 2019-08-09 23:16:24 +02:00
Anthony Lavado
aafa11b48b Merge pull request #1608 from Marenz/patch-1
Add link to feature request hub to readme
2019-08-09 17:08:40 -04:00
Bond-009
a5cb069f26 Update HdHomerunManager.cs 2019-08-09 22:38:31 +02:00
Bond_009
1cad93c276 Use System.Net abstractions instead of raw socket 2019-08-09 22:38:31 +02:00
Bond_009
0116190050 Minor changes 2019-08-09 22:37:44 +02:00
Bond_009
cf7290343f Fix build 2019-08-09 22:36:20 +02:00
Bond_009
9fff4b060e Replace custom code with Asp.Net Core code 2019-08-09 22:36:20 +02:00
Mathias L. Baumann
6c58ac5c55 Add link to feature request hub to readme 2019-08-09 10:00:16 +02:00
dkanada
cf0460c7f9 move comment to separate line 2019-08-07 02:24:56 -07:00
dkanada
779f0c637f Merge pull request #1580 from Bond-009/linklocal
Ignore Ipv6 link-local addresses
2019-08-07 02:18:22 -07:00
Andrew Rabert
dac22887cf Merge pull request #1605 from nvllsvm/fix_docker
Fix Docker build
2019-08-07 00:04:34 -04:00
Andrew Rabert
da01376294 Fix Docker build 2019-08-06 23:57:39 -04:00
Joshua M. Boniface
74f88b3c50 Merge pull request #1602 from anthonylavado/stale-update
Update the Stale-bot config
2019-08-06 13:21:05 -04:00
Anthony Lavado
ff93b162ee Update the Stale-bot config
Updates the Stale bot to mark an issue after 90 days, and allows 14
days before closing the issue.
2019-08-06 12:59:45 -04:00
Tradutor da Silva
89f592687e Translated using Weblate (Portuguese (Brazil))
Currently translated at 95.7% (90 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/
2019-08-04 19:24:29 -04:00
Tamás Mogyorósi
5e6e52d397 Translated using Weblate (Hungarian)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hu/
2019-08-04 19:24:29 -04:00
AndersMachmueller
20cbbd4f4c Translated using Weblate (Danish)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/da/
2019-08-04 19:24:29 -04:00
Michał
b637cdabae Translated using Weblate (Polish)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pl/
2019-08-04 19:24:29 -04:00
Joshua M. Boniface
0f9fd38053 Merge pull request #1593 from nvllsvm/docker_web_master
Docker - make web version more configurable
2019-08-03 15:53:07 -04:00
Bond-009
20f0a8a1c4 Merge pull request #1597 from whooo/filewriter-log
Set log level to debug for HTTP range requests
2019-08-03 15:50:56 +02:00
Erik Larsson
0e6417c9fa Set log level to debug for HTTP range requests 2019-08-03 12:37:02 +02:00
Anthony Lavado
cc4bf60092 Merge pull request #1590 from anthonylavado/null_check
Adds null check on studio/network name
2019-08-02 14:12:17 -04:00
Andrew Rabert
358665d944 Docker - make web version more configurable
This allows for building against jellyfin-web master. Ex.

docker build . --build-arg JELLYFIN_WEB_VERSION=master
2019-08-01 01:03:52 -04:00
Anthony Lavado
d05440d267 Update MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
Co-Authored-By: Claus Vium <cvium@users.noreply.github.com>
2019-07-31 12:21:46 -04:00
dkanada
ca8e0796d9 Merge pull request #1582 from Bond-009/ipnetwork
Include library via NuGet instead of via source
2019-07-30 23:45:32 -07:00
dkanada
3949048dde Merge pull request #1573 from Bond-009/mimetype
Use a dictionary to look up mimetypes
2019-07-30 23:44:58 -07:00
dkanada
bf083c1429 Merge pull request #1579 from Bond-009/ass
Fix #1388 and #1472
2019-07-29 15:15:17 -07:00
Joshua M. Boniface
8c15ac7fab Merge pull request #1583 from jellyfin/EraYaN-fider-readme
Add Fider to README
2019-07-29 14:53:35 -04:00
Erwin de Haan
487ba2b928 Add Fider to README 2019-07-29 20:21:47 +02:00
Anthony Lavado
2fc1f39061 Merge pull request #1555 from jellyfin/EraYaN-stale-app-config
Add Stale app configuration starting point
2019-07-29 13:47:03 -04:00
Erwin de Haan
9e1adec5e0 Update message for stale issues and also ignore feature and enhancement labels 2019-07-29 19:42:38 +02:00
Anthony Lavado
43748d439a Merge pull request #1572 from jellyfin/remove-feature-requests
Remove the issue template for feature requests.
2019-07-29 13:20:07 -04:00
Bond_009
998017a76d Include library via NuGet instead of via source 2019-07-29 16:01:14 +02:00
Bond_009
5c9d041423 Ignore Ipv6 link-local addresses 2019-07-29 13:57:36 +02:00
dkanada
e4644599af Merge pull request #1513 from Bond-009/style2
Fix style issues
2019-07-29 00:16:56 -07:00
Bond_009
e6ef6088ff Fix #1388 and #1472 2019-07-29 00:43:57 +02:00
Erwin de Haan
b4f446fe42 Delete enhancement-request.md 2019-07-28 22:50:41 +02:00
Anthony Lavado
da7abea9aa Merge pull request #1574 from jellyfin/compat-checker-ci
Add the --azure-pipelines switch to the compat checker
2019-07-28 16:48:49 -04:00
Erwin de Haan
9faf035413 Add the --azure-pipelines switch to the compat checker 2019-07-28 21:01:18 +02:00
Bond-009
8b1bd7ac6b Use a dictionary to look up mimetypes 2019-07-28 20:20:03 +02:00
Erwin de Haan
7faf3ab04a Delete feature_request.md 2019-07-28 19:41:36 +02:00
Anthony Lavado
a8014b3942 Merge pull request #1540 from HelloWorld017/sami-fix
Fixed SMI Encoding Bug
2019-07-28 00:42:21 -04:00
dkanada
85b277b872 Merge pull request #1524 from Bond-009/ipaddress
Remove IpAddressInfo and IpEndPointInfo classes
2019-07-27 17:50:17 -07:00
Erik Larsson
a1efe4caca Add DLNA headers if requested by the client.
And remove the code which adds the headers as ResponseHelper.WriteToResponse
will do it.
2019-07-27 19:55:18 +02:00
crankdoofus
c6111a7fb5 Change service install user
The default is Network Service, with advanced option to use Local System
2019-07-27 20:23:22 +10:00
Anthony Lavado
80145cd5a3 Merge pull request #1562 from Bond-009/buffered
Don't copy the complete response stream
2019-07-27 03:01:34 -04:00
Anthony Lavado
d39decf918 Adds null check on studio/network name 2019-07-27 02:30:42 -04:00
crankdoofus
5517d912bf Rework based on review comments 2019-07-25 21:52:44 +10:00
crankdoofus
fbbcba95d3 Update installer name to copy 2019-07-25 21:51:53 +10:00
Bond_009
8270d0cc91 Move IPv6 scope id removal logic to it's own function 2019-07-25 00:23:56 +02:00
Bond_009
ddd1a282ea Remove IpAddressInfo and IpEndPointInfo classes 2019-07-25 00:15:06 +02:00
Bond_009
773af2eef9 Don't copy the complete response stream 2019-07-24 23:46:58 +02:00
Bond-009
e8028de4d7 Merge pull request #1560 from jellyfin/release-10.3.z
Backmerge for 10.3.7
2019-07-24 19:10:04 +02:00
Joshua M. Boniface
595a68b822 Bump version for 10.3.7 2019-07-24 10:48:35 -04:00
Vladimir Jendrol
18bc6c69d5 Translated using Weblate (Slovak)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sk/
2019-07-22 22:28:01 -04:00
tluciomiranda
d56725a43d Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_PT/
2019-07-22 22:28:01 -04:00
Tradutor da Silva
b3aaa9216d Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/
2019-07-22 22:28:01 -04:00
vaheed
ea41155c6b Translated using Weblate (Persian)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fa/
2019-07-22 22:28:01 -04:00
polVRtong
b337df889e Translated using Weblate (Korean)
Currently translated at 97.8% (92 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ko/
2019-07-22 22:28:01 -04:00
exveria1015
00c92e88c5 Translated using Weblate (Japanese)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ja/
2019-07-22 22:28:01 -04:00
Moritz
8c94187c75 Translated using Weblate (German)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/de/
2019-07-22 22:28:01 -04:00
Matsuri
cd504e6ee5 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/
2019-07-22 22:28:01 -04:00
ZhiGang Zhung
6e29b8ad6f Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/
2019-07-22 22:28:01 -04:00
ancarvalho
0d9cdb98f2 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/
2019-07-22 22:28:01 -04:00
Anthony Lavado
c5d9480313 Merge pull request #1552 from cvium/fix_livetv_v2
Disable buffering in HttpClient as it causes big requests to timeout
2019-07-22 02:28:38 -04:00
Erwin de Haan
67e32a2c44 Add Stale app configuration starting point
Link: https://github.com/apps/stale
2019-07-21 01:07:39 +02:00
Claus Vium
dadfc09c01 Add HttpCompletionOption.ResponseHeadersRead to the buffering option to avoid potentially having 2 copies in memory 2019-07-20 13:36:59 +02:00
Claus Vium
886c88576c Use HttpCompletionOption.ResponseHeadersRead and resort to Content-Length header for non-buffered content 2019-07-19 23:22:30 +02:00
Joshua M. Boniface
4ba33eb3e1 Merge pull request #1541 from joshuaboniface/fix-deb-rules
Override the Debian installinit name
2019-07-14 23:40:03 -04:00
Khinenw
59518ec87e Fixed SMI to SRT UTF-16 Encoding bug 2019-07-15 12:20:59 +09:00
Joshua M. Boniface
953f077f9d Override the installinit name
Without this, when building the `jellyfin-nightly` package, it attempts
to find service/init files with the name `jellyfin-nightly` instead of
the proper name. This override prevents this by forcing the name to
`jellyfin`. Required for nightly builds.
2019-07-14 23:11:17 -04:00
Joshua M. Boniface
cf2f5b2026 Merge pull request #1538 from joshuaboniface/epg
Try to fix XmlTvListingsProvider
2019-07-14 17:09:00 -04:00
Joshua M. Boniface
135c16c721 Merge pull request #1537 from joshuaboniface/contenttype
Properly set content type
2019-07-14 17:08:54 -04:00
Joshua M. Boniface
e94fa791a9 Merge pull request #1519 from Bond-009/mergefix
Fix merge errors
2019-07-13 17:32:45 -04:00
Bond_009
5d9fa06675 Cleanup 2019-07-13 17:18:39 -04:00
Bond_009
b294b802a8 Try to fix XmlTvListingsProvider 2019-07-13 17:18:27 -04:00
Bond_009
7bb504d491 Create a new HttpMethod from the function name 2019-07-13 17:12:06 -04:00
Bond_009
b1bd062709 Properly set content type 2019-07-13 17:12:06 -04:00
Joshua M. Boniface
0d3b399b61 Merge pull request #1536 from joshuaboniface/fix-symlinks
Fix broken symlinks in deployment
2019-07-13 15:26:48 -04:00
Joshua M. Boniface
0f8e2600e3 Fix broken symlinks
These were removed somehow in defc5f1cf9b486357b379c610663e1bad48428ad;
restore them to their proper link state.
2019-07-13 14:23:57 -04:00
Joshua M. Boniface
3d71e9b509 Merge pull request #1534 from joshuaboniface/nightly-deb-fix
Use dash or underscore on mv command
2019-07-13 13:29:32 -04:00
Joshua M. Boniface
881f385a61 Use dash or underscore on mv command
Required for nightlies, which would not match this file format with the
underscore then wildcard. Enables nightly builds.
2019-07-13 13:03:50 -04:00
crankdoofus
e31851d25e Update to uninstall silently if already installed 2019-07-13 21:16:56 +10:00
crankdoofus
aff72323c6 Update code for in-place upgrades 2019-07-09 22:56:23 +10:00
Bond-009
a31a396780 Merge pull request #1518 from jellyfin/EraYaN-azp-grant-auth
Update the GitHub token to a Grant Auth token
2019-07-07 12:25:58 +02:00
crankdoofus
8555c5fae1 Correct comment 2019-07-07 09:17:03 +10:00
crankdoofus
da71354e82 Remove Emby migration section, include License 2019-07-07 09:13:27 +10:00
crankdoofus
3d0e7f6cb6 Include License file with installation 2019-07-07 09:10:35 +10:00
Bond_009
c7d12cc481 Fix merge errors 2019-07-07 00:43:43 +02:00
Erwin de Haan
440177a43d Update the GitHub token to a Grant Auth token. 2019-07-07 00:20:37 +02:00
Bond-009
953eb6e906 Merge pull request #1517 from jellyfin/EraYaN-azp-github-token-name
Update the GitHub connection name to use the GitHub App token
2019-07-07 00:08:27 +02:00
Erwin de Haan
fc55b44e4b Update the GitHub connection name to use the GitHub App token 2019-07-07 00:03:59 +02:00
Bond-009
f2a56fcd80 Merge pull request #1516 from jellyfin/EraYaN-vacuum-merge-fix
Move VACUUM command to fix merge error
2019-07-06 23:57:27 +02:00
Erwin de Haan
0dbc294836 Move VACUUM command to fix merge error
This fixes a syntax error.
2019-07-06 23:50:06 +02:00
Joshua M. Boniface
3b49c1bac0 Merge pull request #1515 from jellyfin/release-10.3.z
Backmerge for 10.3.6
2019-07-06 17:48:48 -04:00
Bond-009
82f041d050 Merge branch 'master' into release-10.3.z 2019-07-06 23:08:52 +02:00
Joshua M. Boniface
4f17ed961e Merge pull request #1514 from Bond-009/httpclient2
Fix issues with HttpClientManager
2019-07-06 17:01:35 -04:00
Joshua M. Boniface
ba551b48e1 Merge pull request #1111 from EraYaN/azp-hardcoded-try
Azure Pipelines DownloadBuildArtifacts@0 Experiment
2019-07-06 14:21:35 -04:00
Bond_009
5fc4ad6c4e Address comments 2019-07-06 20:04:45 +02:00
Bond_009
b117b364f2 Remove duplicate code 2019-07-06 20:04:45 +02:00
Bond_009
3603c64fa6 Use HttpResponseHeaders instead of a dictionary 2019-07-06 20:04:45 +02:00
Bond_009
d405a400aa Fixes issues with HttpClientManager 2019-07-06 20:04:42 +02:00
Joshua M. Boniface
54c6f02ebb Merge pull request #1455 from ferferga/release-10.3.z
Vacuum databases at startup
2019-07-06 13:57:18 -04:00
Joshua M. Boniface
b3f9d04501 Bump version for 10.3.6 2019-07-06 13:34:38 -04:00
Bond_009
ab7ef9c9cb Fix style issues 2019-07-06 16:15:38 +02:00
Bond_009
0f897589ed Streamline authentication proccess 2019-07-06 14:52:24 +02:00
crankdoofus
cea6a2217e Correct Service handling & LocalAppData folder
The service is now completely controlled by nssm as with the install-jellyfin.ps1
The LocalAppData had the global context, its now 
Corrected order of Mandatory and Optional components.
2019-07-06 18:34:48 +10:00
crankdoofus
dc3eceec6a Changed order to include install scripts in installer 2019-07-06 18:02:00 +10:00
crankdoofus
a6819ffd1d Cleaned up code 2019-07-06 12:19:57 +10:00
crankdoofus
de9ee10abc Uncomment accidental commenting of compilation 2019-07-06 12:18:20 +10:00
crankdoofus
43989800ba Added -Force to nsis extraction 2019-07-06 12:16:34 +10:00
crankdoofus
1fd827fa77 Create jellyfin.nsi 2019-07-06 11:43:20 +10:00
crankdoofus
3b9766f58c Added option for NSIS
This change will 
1. download NSIS zip, 
2. unzip in temp folder, 
3. use nsis to build the installer
2019-07-06 11:41:33 +10:00
Erwin de Haan
2c8df07753 Fix some task names. 2019-07-05 13:03:34 +02:00
Erwin de Haan
08421311b9 Switch download order around. 2019-07-05 12:10:57 +02:00
Erwin de Haan
dc68fa2c8b Disable seperate build and restore. 2019-07-05 12:06:01 +02:00
Erwin de Haan
ff373621b3 Switch to next gen artifacts. 2019-07-05 12:02:35 +02:00
Erwin de Haan
272691aacd Switched to specific download. 2019-07-05 11:37:14 +02:00
Erwin de Haan
46623bc985 Try with hardcoded name. 2019-07-05 11:32:25 +02:00
Erwin de Haan
3462147195 Switched to latest ubuntu image and hardcoded definitionId. 2019-07-05 11:17:48 +02:00
Erwin de Haan
268fe5efe8 Small attempts at making previous build downloads work. 2019-07-05 11:11:46 +02:00
dkanada
0e0c70f782 Merge pull request #1505 from trumblejoe/patch-1
Qualified Namespace of pscredential
2019-07-04 20:18:46 -07:00
Erwin de Haan
acf52b9b55 Cleanup extra spaces. 2019-07-04 20:55:49 +02:00
Erwin de Haan
7587fe56d8 Moved VACUUM down to the end of the list. 2019-07-04 20:54:57 +02:00
Anthony Lavado
ab34a95142 Merge pull request #1473 from DrPandemic/apply-deprecation-WebRequest-on-10.3.z
Apply deprecation web request on 10.3.z
2019-07-04 14:51:24 -04:00
dkanada
9e9952d81f Merge pull request #1247 from bugfixin/master
Adjust detection of 'sample' in filenames to use regex boundaries
2019-07-04 11:38:01 -07:00
dkanada
4f2d601f02 Merge pull request #1167 from Bond-009/eol
Force LF line endings
2019-07-02 13:02:29 -07:00
dkanada
e722801f80 Merge pull request #956 from Bond-009/db
Simplify db code
2019-07-02 11:56:13 -07:00
dkanada
6cf9204219 Merge pull request #1500 from dkanada/password
Add optional password field on user creation
2019-07-02 11:55:44 -07:00
trumblejoe
b719ca5a33 Qualified Namespace of pscredential
Qualified Namespace of pscredential, otherwise script fails in Powershell builds <3.0.
2019-07-02 13:29:28 -04:00
Bond_009
29ae7b9aeb Add docs 2019-07-01 18:24:35 +02:00
Bond_009
45c13141f9 Address comments 2019-07-01 17:59:01 +02:00
dkanada
9079b3e8da add optional password field on user creation 2019-06-30 01:58:01 -07:00
Andrew Rabert
0ee40cb636 Merge pull request #1495 from joshuaboniface/better-restart-script
Add nicer restart script
2019-06-29 19:48:54 -04:00
Joshua M. Boniface
62105c249f Use which to find the service binary path 2019-06-28 11:15:08 -04:00
Joshua M. Boniface
a629f209b9 Make message wording more consistent 2019-06-28 11:06:55 -04:00
Bond_009
ecb8d8991b Fix whitespace 2019-06-28 12:22:33 +02:00
Bond_009
2e4c0fee77 Add removed line 2019-06-28 12:16:51 +02:00
Bond_009
d961278b3d Reduce amount of raw sql 2019-06-28 12:14:27 +02:00
Bond_009
db2765aae5 Last bit of cleanup 2019-06-28 12:14:27 +02:00
Bond_009
7898af4ceb Reworked PRAGMA statements use 2019-06-28 12:14:27 +02:00
Bond_009
edfd2d0cd9 Fix startup 2019-06-28 12:14:27 +02:00
Bond_009
d00ad28efd Address comments 2019-06-28 12:14:27 +02:00
Bond-009
02b864e41b Last line? 2019-06-28 12:14:27 +02:00
Bond-009
e88ebd748d Final fixes 2019-06-28 12:14:27 +02:00
Bond-009
b6954f3bfd More 2019-06-28 12:14:27 +02:00
Bond-009
27c29bbb4c Back to a single connection 2019-06-28 12:13:34 +02:00
Bond-009
30842656a7 Properly dispose 2019-06-28 12:12:54 +02:00
Bond-009
e5248cfaa2 Properly dispose 2019-06-28 12:12:54 +02:00
Bond-009
c30ba14c1f Use a connection pool instead of creating new connections 2019-06-28 12:12:54 +02:00
Bond-009
cec22ad10d Simplify db code 2019-06-28 12:12:54 +02:00
Joshua M. Boniface
c08c0272b5 Add nicer restart script
The old restart script was buggy, as reported in #1320. This updated
script seems to work far more reliably and conforms to the existing
jellyfin-sudoers packages sudo configuration.
2019-06-27 18:05:03 -04:00
dkanada
c52e8a2027 Merge pull request #1394 from joern-h/bugfix-issue1347
Check if an item is a child of an EnabledFolder
2019-06-26 01:19:34 -07:00
Jörn
1fd8164756 fix issue #1347 introduced in pr #930 2019-06-26 01:13:54 -07:00
dkanada
b3b08fecb2 Merge pull request #1453 from Bond-009/cleanup2
Improve main code flow
2019-06-26 01:03:09 -07:00
dkanada
3c16c34386 Merge pull request #1485 from DrPandemic/fix-skia-segfault
Fix skia segfault
2019-06-25 12:44:29 -05:00
DrPandemic
394d96246b Check path before opening image 2019-06-24 20:13:07 -04:00
Andrew Rabert
fc439cc02a Merge pull request #1448 from joshuaboniface/nice-userconfig-paths
Use the username for the user config path
2019-06-24 12:54:24 -04:00
SenorSmartyPants
18e6cd429a Update TVDB provider to search based on series display order 2019-06-22 16:11:47 -05:00
dkanada
1b2621cd30 Merge pull request #1454 from Bond-009/webresource
Simplify file serving code
2019-06-21 23:33:37 -07:00
Anthony Lavado
084854d71d Merge pull request #1478 from cvium/fix_tvdb_again
Wait for the async authentication to finish when the JTW token expires
2019-06-22 02:08:46 -04:00
Claus Vium
c2ab0ad641 Wait for the async authentication to finish when the JTW token expires 2019-06-21 19:08:04 +02:00
dkanada
dbc2cda9d4 Merge pull request #1369 from teacupx/master
Enable Exynos MFC encoder and fix transcoding bitrate control
2019-06-20 16:55:09 -07:00
dkanada
65fa61a636 add comment explaining GetMinBitrate 2019-06-20 16:44:27 -07:00
Erwin de Haan
04784b4e43 Create Media Playback issue template (#1451)
Added media playback issue template. We might want to add some more information requests in there.
2019-06-19 12:41:52 -04:00
Claus Vium
7eb94e9674 Update MediaBrowser.Common/Net/IHttpClient.cs
Co-Authored-By: Bond-009 <bond.009@outlook.com>
2019-06-18 22:21:07 -04:00
Bond-009
0a5550b13d Remove more unused stuff 2019-06-18 22:20:34 -04:00
Bond-009
067200be83 Remove usage of depricated 'WebRequest'
Ref: https://docs.microsoft.com/en-us/dotnet/api/system.net.webrequest?view=netframework-4.7.2
2019-06-17 19:35:05 -04:00
Joshua M. Boniface
2baddc1709 Merge pull request #1463 from Bond-009/deps
Update deps
2019-06-14 15:43:59 -04:00
Bond_009
5554595255 Update deps 2019-06-14 18:49:57 +02:00
Bond_009
65a0ca2f32 Improvements to InstallationManager 2019-06-14 18:38:14 +02:00
Anthony Lavado
d4a42a1680 Merge pull request #1080 from Bond-009/httpclient
Remove usage of deprecated 'WebRequest'
2019-06-14 09:19:56 -04:00
Bond-009
af099a9b53 Update Emby.Server.Implementations/ApplicationHost.cs
Co-Authored-By: Jean-Samuel Aubry-Guzzi <DrPandemic@users.noreply.github.com>
2019-06-14 08:21:06 +02:00
Bond-009
6ebac0e500 Update Emby.Server.Implementations/ApplicationHost.cs
Co-Authored-By: Jean-Samuel Aubry-Guzzi <DrPandemic@users.noreply.github.com>
2019-06-14 08:20:52 +02:00
Anthony Lavado
b25c08e79a Merge pull request #1156 from Bond-009/sep
Small cleanups here and there
2019-06-13 22:27:00 -04:00
Bond-009
8fd47dd658 Merge pull request #1457 from TrisMcC/fix-path-substitution-1446
Stop path substitution in SubtitleEncoder - Fix #1446
2019-06-13 15:18:01 +02:00
Joshua M. Boniface
687255aa31 Merge pull request #1442 from jellyfin/release-10.3.z
Backmerge for Release 10.3.4 and 10.3.5
2019-06-12 23:30:57 -04:00
Tristan McCann
5c1fbfca03 Stop path substitution in SubtitleEncoder
This fixes jellyfin/jellyfin#1446. I am not an expert in this section of
code, but I cannot think of a need to do path substitution during
subtitle encoding.
2019-06-11 21:36:42 -04:00
ferferga
b136f14084 Vacuum databases at startup 2019-06-10 11:31:38 +02:00
Joshua M. Boniface
d5fe82314e Bump version for 10.3.5 2019-06-09 21:47:45 -04:00
Bond_009
253e72f667 Simplify file serving code 2019-06-10 00:53:16 +02:00
Bond_009
aa30227545 Improve main code flow
Improved the way how some parts of the code depend on eachother
Fixed some style issues
2019-06-09 23:51:52 +02:00
Joshua M. Boniface
06834fefef Merge pull request #1447 from joshuaboniface/implement-invalidauth
Implement InvalidAuthProvider
2019-06-09 15:36:25 -04:00
Joshua M. Boniface
2946ae1009 Revert "Don't set a default reset provider"
This reverts commit c230d49d7c.

This reenables an edge case where an admin might want to reset, with
the default auth provider, the password of an externally-provided
user so they could "unlock" the account while it was failing. There
might be minor security implications to this, but the malicious
actor would need FS access to do it (as they would with any password
resets) so it's probably best to keep it as-is.

Removing this in the first place was due to a misunderstanding
anyways so no harm.
2019-06-09 15:29:43 -04:00
Joshua M. Boniface
4b8f735cb8 Remove superfluous conditional
This wasn't needed to prevent updating the policy on-disk from my
tests and can be removed as suggested by @Bond-009
2019-06-09 13:57:49 -04:00
Joshua M. Boniface
c230d49d7c Don't set a default reset provider 2019-06-09 13:46:53 -04:00
Joshua M. Boniface
20e2cb2d86 Use SecurityException for auth failure 2019-06-09 13:45:51 -04:00
Joshua M. Boniface
b70083f3b3 Apply suggestions from code review
Co-Authored-By: Claus Vium <cvium@users.noreply.github.com>
Co-Authored-By: Bond-009 <bond.009@outlook.com>
2019-06-09 13:41:14 -04:00
Joshua M. Boniface
74ef389879 Add nicer log message and comment 2019-06-09 11:07:35 -04:00
Joshua M. Boniface
d1d0ddf62f Use the username for the user config path
Use the username to construct the UserConfigurationDirectory,
instead of the user ID, and move the old ID-based path to the new
path if needed when loading (temporary transitional code). Removes
administrator guesswork as to what user each directory belongs to,
which I found very annoying when investigating user configs.
2019-06-08 23:54:26 -04:00
Joshua M. Boniface
d78a55adb4 Implement InvalidAuthProvider
Implements the InvalidAuthProvider, which acts as a fallback if a
configured authentication provider, e.g. LDAP, is unavailable due
to a load failure or removal. Until the user or the authentication
plugin is corrected, this will cause users with the missing provider
to be locked out, while throwing errors in the logs about the issue.

Fixes #1445 part 2
2019-06-08 22:54:31 -04:00
Bond_009
77ecb0a70c Make script executable again 2019-06-08 19:57:46 +02:00
Bond-009
cb07822aa3 Add execute permissions back 2019-06-08 19:56:08 +02:00
Bond_009
defc5f1cf9 Force LF line endings 2019-06-08 19:56:08 +02:00
Andrew Rabert
6f99ed3955 Merge pull request #1443 from jellyfin/qemu
Docker - Update arm* Dockerfiles for latest multiarch
2019-06-07 18:41:35 -04:00
Andrew Rabert
247a5e12ab Docker - Update arm* Dockerfiles for latest multiarch
Relates to this change 7bdafb96ee
2019-06-07 00:00:14 -04:00
Joshua M. Boniface
855911333a Bump version for 10.3.4 2019-06-06 22:45:37 -04:00
Anthony Lavado
127bfc7d3b Merge pull request #1437 from pjeanjean/master
Fix issue #1436: media folders appear empty unless user has all libraries access
2019-06-06 17:21:20 -04:00
pjeanjean
7919dd81da Skip user permission checking for UserRootFolder
Fix #1436
UserRootFolders are used to represent virtual folders that exist outside
of libraries. As such, it doesn't make sense to check if a user has the
right to access their library (named `Media Folders`).
2019-06-06 08:30:56 +02:00
Bond-009
e3a3aebbf6 Merge pull request #1440 from cvium/fix-build
Change UsCulture to InvariantCulture
2019-06-05 21:31:09 +02:00
Claus Vium
c05b7c382a Change UsCulture to InvariantCulture 2019-06-05 20:59:06 +02:00
Anthony Lavado
d7aaa1489c Merge pull request #1427 from dkanada/fanart
Move fanart image provider to plugin
2019-06-05 01:19:54 -04:00
Anthony Lavado
aee3360841 Merge pull request #1366 from Bond-009/warn3
Fix more warnings
2019-06-05 01:17:21 -04:00
Anthony Lavado
256f44a870 Merge pull request #994 from Bond-009/tasks
Remove Tasks wrapped in a Task
2019-06-05 01:11:25 -04:00
Anthony Lavado
f631b2ecdc Merge pull request #1159 from Bond-009/streamjob
Trying to make sense of the streaming code
2019-06-04 00:20:43 -04:00
Anthony Lavado
a623dd1921 Merge pull request #1368 from dkanada/drives
Only return useful drives
2019-06-04 00:19:35 -04:00
dkanada
b768ad978e split the new command to more than one line 2019-06-02 21:49:12 -07:00
Juvenal Yescas
6b6776042c Translated using Weblate (Spanish (Mexico))
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_MX/
2019-06-01 20:50:19 -04:00
Julio García
4adaeee054 Translated using Weblate (Spanish)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es/
2019-06-01 20:50:19 -04:00
Hyunsu Nam
a2d9420139 Translated using Weblate (Korean)
Currently translated at 97.8% (92 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ko/
2019-06-01 20:50:19 -04:00
exveria1015
3431a85adf Translated using Weblate (Japanese)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ja/
2019-06-01 20:50:18 -04:00
dracocephalum
430483c7a1 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hant/
2019-06-01 20:50:16 -04:00
Frank
3ba709fcc3 Fix #1432. Add support for encoding with libx265 and hevc_nvenc. 2019-06-01 15:46:41 -07:00
Bond-009
ce1fa42f9d Merge branch 'master' into tasks 2019-06-01 17:06:01 +02:00
Bond_009
08ac5b6ec3 Fix build 2019-06-01 11:34:28 +02:00
Bond-009
a6f9ceedd8 Fix more warnings 2019-06-01 11:31:27 +02:00
Anthony Lavado
e1da046960 Merge pull request #1426 from jellyfin/cvium-fix-tvdb-refresh
Fix inverted comparison in the tvdb token refresh logic
2019-05-31 21:27:33 -04:00
dkanada
09dfa071dc move fanart image provider to plugin 2019-05-31 01:48:20 -07:00
Claus Vium
a756026962 Fix inverted comparison in the tvdb token refresh logic 2019-05-31 07:24:52 +02:00
Anthony Lavado
75260a960b Merge pull request #1406 from DrPandemic/fix-pin-update
Format the PIN when updating it
2019-05-31 00:58:53 -04:00
exveria1015
b1f764984f Added translation using Weblate (Japanese) 2019-05-30 18:28:11 -04:00
DrPandemic
69ee49bee6 Format correctly the PIN when updating it 2019-05-25 13:46:55 -04:00
erikasne6152
2aed2d164b Translated using Weblate (Lithuanian)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/lt/
2019-05-24 14:28:14 -04:00
Bond-009
2d011b781e Merge pull request #1383 from jellyfin/release-10.3.z
Backmerge for Release 10.3.3
2019-05-18 12:56:42 +02:00
Joshua M. Boniface
1bf3a26a61 Bump version for 10.3.3 2019-05-17 23:12:21 -04:00
Joshua M. Boniface
c5760b3a40 Merge pull request #1380 from bugfixin/dlna-photo-thumb-fix
Improve Photo rendering in DLNA
2019-05-17 22:55:43 -04:00
Joshua M. Boniface
4de8bf3295 Merge pull request #1338 from cvium/fix_extras
Enforce a specific folder structure for Extras to avoid misidentification
2019-05-17 09:00:22 -04:00
dkanada
4a9b349c04 only return useful drives 2019-05-16 16:45:56 -07:00
bugfixin
87c8f19f19 Move DLNA thumbnail element to after larger image elements 2019-05-16 18:00:38 +00:00
Claus Vium
0ef52c739e Review changes
Untested
2019-05-16 07:27:38 +02:00
Joshua M. Boniface
0a9a6b949c Merge pull request #1372 from DrPandemic/fix-pin
Fix broken pin in 10.3.z
2019-05-14 21:54:28 -04:00
Joshua M. Boniface
71f81c5fb0 Merge pull request #1367 from Bond-009/photo
Ignore casing photo extensions
2019-05-14 21:25:46 -04:00
DrPandemic
c22068d6b1 Fix pin bug introduced in 10.3.z.
The issue is that the new easyPassword format prepends the hash
function. This PR extract the hash from "$SHA1$_hash_".
2019-05-11 19:53:34 -04:00
JMCC
012e4a3e63 Fix transcode bitrate control 2019-05-11 17:19:20 +02:00
JMCC
5d85076ad5 Enable Exynos V4L2-m2m HW encoder 2019-05-11 17:17:32 +02:00
Bond-009
35d7e97258 Ignore casing photo extensions 2019-05-11 11:55:41 +02:00
Joshua M. Boniface
bbc1a86b57 Merge pull request #1306 from oddstr13/pr-sudoless-build-1
Move artifact chown inside docker to avoid sudo
2019-05-10 09:25:41 -04:00
Joshua M. Boniface
89537abdc4 Merge pull request #1293 from Bond-009/query-time
Fix query time logging
2019-05-10 09:19:05 -04:00
Anthony Lavado
abfc41f382 Merge pull request #1363 from joshuaboniface/readme
Update getting-help link in README
2019-05-09 23:44:47 -04:00
Joshua Boniface
84ac6ea12a Update getting-help link in README 2019-05-09 18:57:40 -04:00
Bond-009
cd83d80f2b Merge pull request #1294 from DrPandemic/fix-download-non-ascii
Fix non-ascii filename downloads
2019-05-09 17:30:19 +02:00
Bond-009
d9c159122f Merge pull request #1229 from voodoos/cleanup/SocketSharp
Cleaning WebSocketSharp continued
2019-05-09 17:16:51 +02:00
Jean-Samuel Aubry-Guzzi
12721eb7dd Fix non-ascii filename downloads
Follow https://tools.ietf.org/html/rfc5987#section-3.2.2 to encode
non-ascii filenames in HTTP Content-Disposition header.
2019-05-07 19:43:04 -04:00
Claus Vium
b8a09339cd Enforce extras folder structure according to Emby's wiki 2019-05-02 08:14:00 +02:00
Odd Stråbø
3634d367c1 Move artifact chown inside docker to avoid sudo 2019-05-01 20:32:15 +02:00
Claus Vium
c1daea0ec7 Change owner and parent id of extras to the main media item 2019-05-01 07:47:22 +02:00
Joshua M. Boniface
2bc378a9c3 Merge pull request #1337 from jellyfin/release-10.3.z
Backmerge for 10.3.2 release
2019-04-30 23:35:11 -04:00
Joshua Boniface
e8196fed7c Bump version for 10.3.2 2019-04-30 20:18:54 -04:00
Joshua M. Boniface
477702fbb9 Merge pull request #1324 from joshuaboniface/arm64
Add arm64 packaging for Debuntu
2019-04-30 20:07:41 -04:00
Joshua M. Boniface
2216a271bb Merge pull request #1335 from Bond-009/ffmpeglimit
Limit amount of ffmpeg processes extracting images at once
2019-04-30 20:06:24 -04:00
Bond-009
91cd7d2f6b Limit amount of ffmpeg processes extracting images at once 2019-04-30 23:35:39 +02:00
Anthony Lavado
4e681d25d9 Merge pull request #1334 from Bond-009/musicbrainzfix
Iterate over IEnumerable before disposing
2019-04-30 16:56:54 -04:00
Bond-009
06cc2891de Merge pull request #1310 from bugfixin/progressivestreamingservice-unreachable
Remove unreachable code from BaseProgressiveStreamingService
2019-04-30 22:20:54 +02:00
Bond-009
682432f55a Iterate over IEnumerable before disposing 2019-04-30 22:18:40 +02:00
Anthony Lavado
7c4cb5ec58 Merge pull request #1333 from bugfixin/easypinfix
Fix incorrect hasPassword flag when easy pin set
2019-04-30 15:36:25 -04:00
bugfixin
1df73fdeba Fix incorrect hasPassword flag when easy pin set 2019-04-30 19:16:53 +00:00
Bond-009
21ba8a0593 Merge pull request #1332 from cvium/fix_tvdb_ep_provider
Make the TvdbEpisodeProvider class Public
2019-04-30 20:59:18 +02:00
Claus Vium
08ed52eb72 Make the TvdbEpisodeProvider class Public 2019-04-30 20:08:59 +02:00
Anthony Lavado
99700e1b95 Merge pull request #1327 from joshuaboniface/disco
Support libssl1.1 for Ubuntu Disco
2019-04-30 02:38:21 -04:00
Joshua M. Boniface
4e0be95368 Merge pull request #1305 from bugfixin/passwordless-form-encoded
Fix passwordless authentication with non-json content-types
2019-04-29 23:07:04 -04:00
Joshua Boniface
c8a59c8343 Support libssl1.1 for Ubuntu Disco 2019-04-29 23:03:57 -04:00
Joshua Boniface
2b2a2ed708 Add arm64 packaging for Debuntu 2019-04-29 00:56:17 -04:00
bugfixin
a827a2fbcc Remove unreachable code and const trySupportSeek within BaseProgressiveStreamingService 2019-04-25 19:14:33 +00:00
Anthony Lavado
f97f6b8061 Merge pull request #1296 from Bond-009/fix1234
Fix #1234
2019-04-25 01:37:02 -04:00
Bond-009
61d7bed181 Merge pull request #1304 from jellyfin/release-10.3.z
Backmerge 10.3.1
2019-04-25 07:27:37 +02:00
bugfixin
844ea9d77e Don't coalesce empty strings to null in StringMapTypeDeserializer 2019-04-25 04:36:28 +00:00
Bond_009
71479286e9 Fix #1234 2019-04-24 19:56:57 +02:00
Bond_009
a9337033c1 Fix query time logging 2019-04-24 15:25:22 +02:00
Bond-009
a0e61ee67f Merge pull request #1274 from bugfixin/content-type-fix
Prevent null reference when request content type is x-www-form-urlencoded
2019-04-24 14:54:18 +02:00
Claus Vium
28c2ac528d Re-add content length, semi revert of changes in #1010 (#1287)
* Re-add content length, semi revert of changes in #1010
2019-04-24 14:06:54 +02:00
bugfixin
08d3a5d2fe Fix null reference when request content type is application/x-www-form-urlencoded 2019-04-21 19:29:05 +00:00
Joshua Boniface
696a36b4a5 Update submodule for 10.3.1 2019-04-20 15:59:50 -04:00
Joshua Boniface
5fb4922c6f Bump version to 10.3.1 2019-04-20 14:24:40 -04:00
Joshua M. Boniface
3738f95871 Merge pull request #1258 from Bond-009/fixpluginload
Handle exception when loading unsupported assembly
2019-04-20 12:35:57 -04:00
Joshua M. Boniface
6797bdec04 Merge pull request #1264 from jellyfin/fix-empty-pw-migration
Fix comparison for empty password migration
2019-04-20 12:20:22 -04:00
Claus Vium
764c6d5461 Fix comparison for empty password migration 2019-04-20 17:50:34 +02:00
Bond-009
6973182ade Fix more possible exceptions 2019-04-20 17:47:11 +02:00
Bond-009
f62af07381 Handle exception when loading unsupported assembly
Fixes #1256
2019-04-20 17:47:11 +02:00
Bond-009
a8da122fb3 Merge pull request #1252 from jellyfin/release-10.3.z
Backmerge release 10.3.0
2019-04-20 12:25:29 +02:00
bugfixin
da842d5a73 Fix incorrect escaping in regex pattern 2019-04-19 18:35:28 +00:00
Joshua Boniface
46c37c0ae8 Bump version to 10.3.0 (release) 2019-04-19 14:25:29 -04:00
Joshua Boniface
4ad71766fc Merge branch 'translations' into release-10.3.z 2019-04-19 14:19:22 -04:00
Weblate
9d60cc8c66 Rename Chinese (Traditional) file 2019-04-19 13:59:04 -04:00
bugfixin
0794a3edf4 Adjust detection of 'sample' in filenames to use regex boundaries 2019-04-19 17:53:51 +00:00
Libor Filípek
4a6243096a Translated using Weblate (Czech)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cs/
2019-04-19 13:38:32 -04:00
Heldenkrieger01
10cbdc8e8e Translated using Weblate (Swiss German)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/gsw/
2019-04-19 12:09:39 -04:00
SaddFox
4886fc467c Translated using Weblate (Slovenian)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sl/
2019-04-19 12:09:38 -04:00
WWWesten
8e2827cc39 Translated using Weblate (Kazakh)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/kk/
2019-04-19 12:09:37 -04:00
Βασίλης Μουρατίδης
395d2e4917 Translated using Weblate (Greek)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/el/
2019-04-19 12:09:37 -04:00
Dan Johansen
d31f5229da Translated using Weblate (Danish)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/da/
2019-04-19 12:09:37 -04:00
Libor Filípek
d6622818dc Translated using Weblate (Czech)
Currently translated at 100.0% (94 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cs/
2019-04-19 12:09:37 -04:00
tinganhsu
ba684d6d3a Translated using Weblate (Chinese (Traditional))
Currently translated at 96.8% (91 of 94 strings)

Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hant/
2019-04-19 12:09:36 -04:00
tinganhsu
90c04a4640 Added translation using Weblate (Chinese (Traditional)) 2019-04-19 00:32:52 -04:00
Joshua M. Boniface
06a1e1f166 Merge pull request #1244 from joshuaboniface/hotfix-authapi
Hotfix authapi
2019-04-18 17:58:54 -04:00
Anthony Lavado
7bea62adbf Merge pull request #1245 from Bond-009/updateevent
Remove unused event
2019-04-18 14:16:48 -04:00
Joshua Boniface
31ad366aa9 Implemented suggested conditional 2019-04-18 10:24:08 -04:00
Joshua Boniface
10f33b0273 Update conditional to be correct 2019-04-18 09:31:30 -04:00
Bond-009
8f703f4744 Remove unused event
Release builds were failing because of this unused event.
2019-04-18 13:19:16 +02:00
Joshua Boniface
eaa1ac8013 Apparently strings can't be !'d 2019-04-17 22:49:17 -04:00
Joshua Boniface
ca3bb308b3 Add the proper Class too 2019-04-17 22:46:26 -04:00
Joshua Boniface
e790f024c2 Return MethodNotAllowedException if Pw is not set
Don't accept pre-hashed (not-plaintext) passwords as the auth
provider no longer supports this due to sha1+salting the passwords
in the database.
2019-04-17 22:33:00 -04:00
Joshua Boniface
250e0c75df Add MethodNotAllowedException with code 405 2019-04-17 22:31:06 -04:00
Joshua M. Boniface
c3532b92f7 Merge pull request #1158 from Bond-009/httpclean
Reduce complexity http routes
2019-04-17 22:12:17 -04:00
Joshua M. Boniface
0539861dc0 Merge pull request #1182 from Bond-009/deepcopy
Speed up DeepCopy
2019-04-17 22:11:53 -04:00
Joshua M. Boniface
deedf2a36c Merge pull request #1224 from Terror-Gene/patch-1
Fix missing Unraid cache mount and name capitalization
2019-04-17 22:11:30 -04:00
Joshua M. Boniface
cde7375049 Merge pull request #1242 from Bond-009/metadata
Fix metadata path save
2019-04-17 09:26:04 -04:00
Bond-009
e89c8dbf76 Use CultureInfo.InvariantCulture 2019-04-17 15:23:03 +02:00
Bond_009
c7fedfbca3 Fix metadata path save 2019-04-17 15:09:31 +02:00
Anthony Lavado
b031a55a59 Merge pull request #1225 from DrPandemic/update-build-path-help
Fix help message to indicate the right output folder
2019-04-16 16:20:12 -04:00
Bond-009
f520831025 Merge pull request #1239 from anthonylavado/fix-ident
Clean up UDP responders, and move ProductName to Public endpoint
2019-04-16 11:56:38 +02:00
Anthony Lavado
2f0719a883 Move the definition of ProductName to the correct class
Missed moving this from one class to the other.
2019-04-16 01:38:00 -04:00
Anthony Lavado
34ab99caf1 Move the ProductName to the public endpoint
Moves the ProductName field over from the private system/info point to
the public one, for easier identification
2019-04-16 01:16:02 -04:00
DrPandemic
65f9141764 Update the help message to indicate the right output folder 2019-04-15 17:51:26 -04:00
Anthony Lavado
b2f94c0e40 Remove the old message responders
Leaves only an answer to "Who is Jellyfin", removing older ones for
EmbyServer and MediaBrowser_v2.
2019-04-15 00:21:14 -04:00
VooDooS
ba12d96d23 Removed wrapping of HeaderNames fields 2019-04-12 15:25:18 +02:00
VooDooS
bb807554e2 Replace CRLF injection mitigation by use of .NET ip parsing 2019-04-12 13:48:12 +02:00
VooDooS
56d1050bac Replace custom ip "normalization" by methods from IPAddress 2019-04-11 17:49:18 +02:00
VooDooS
a6e1b23eb0 Simplify headers use in WSS 2019-04-11 17:49:14 +02:00
VooDooS
5f6ab836de Extend Microsoft.Net.Http.Headers.HeaderNames 2019-04-11 15:35:06 +02:00
Terror-Gene
a9f790e101 Fix directory capitalization 2019-04-11 04:00:46 +09:30
Terror-Gene
f888c4b641 Fix missing Unraid cache mount
Cache folder was not mounted outside of the Docker image since its separation from the config folder.
Config HostDir was only updated for consistency, previous directory was overridden by unraid into the appdata/appname folder anyway.
Name capitalization was corrected as this is only used by new installations & does not affect current installations/updates.
2019-04-11 03:19:05 +09:30
Ulysse
a1d50a6d05 Clean WebSocketSharpRequest.PathInfo (#1212)
* rm useless ResolvePathInfoFromMappedPath method

* rm useless NormalizePathInfo method

* Use request.Path instead of RawUrl

* Removing unused `HandlerFactoryPath` field

* Use an  expression body definition and rm field `pathInfo`

* More (syntactic) sugar

* Who needs blocks in cases ?
2019-04-09 20:19:27 +02:00
Anthony Lavado
d7df2ac60c Merge pull request #1210 from DrPandemic/fix-readme-link
Fix README documentation link
2019-04-09 12:23:07 -04:00
Bond-009
dcae3daf43 Merge pull request #1218 from EraYaN/disable-azure-ci-compatcheck
Disable dotnet_compat part of pipeline
2019-04-09 07:45:15 +02:00
Andrew Rabert
941ee53e7a Merge pull request #1211 from Terror-Gene/patch-2
Update Unraid Docker icon
2019-04-08 18:53:57 -04:00
Erwin de Haan
7b4e16bb8f Disabled dotnet_compat part of pipeline. 2019-04-09 00:43:25 +02:00
Terror-Gene
c72393c970 Updated Unraid Docker icon
Logo was set to use emby, but binhex has since added the jellyfin logo.
2019-04-08 14:56:42 +09:30
DrPandemic
f96d1e9e69 Fix README documentation link 2019-04-07 22:26:27 -04:00
Bond-009
2f33e99006 Speed up DeepCopy 2019-04-02 18:17:50 +02:00
Vasily
79d9b8e693 Merge pull request #1168 from Bond-009/io
Improve IO code
2019-04-01 16:20:23 +03:00
Vasily
91e3b3b491 Merge pull request #1176 from Bond-009/namingdep
Remove unused dependency for Emby.Naming
2019-04-01 16:19:44 +03:00
Bond_009
f2e2065fd4 Remove unused dependency for Emby.Naming 2019-03-31 15:25:30 +02:00
Bond_009
f911fda34f Merge ifs 2019-03-29 20:34:42 +01:00
Bond_009
41df562419 Improve IO code
* Style changes
* Remove remnants of SMB support
* Use `GetInvalidFileNameChars` instead of rolling our own
* Remove possible unexpected behaviour with async file streams
* Remove some dead code
2019-03-28 23:26:43 +01:00
Bond-009
73a9079ee2 Merge branch 'master' into httpclient 2019-03-27 19:43:02 +01:00
Bond_009
9aaeb19418 Self-documenting code 2019-03-27 17:05:08 +01:00
Claus Vium
be86ea2982 Update MediaBrowser.Common/Net/IHttpClient.cs
Co-Authored-By: Bond-009 <bond.009@outlook.com>
2019-03-27 16:34:56 +01:00
Bond-009
d0fbd260d5 Merge branch 'master' into httpclient 2019-03-27 16:34:26 +01:00
Bond_009
b69b19ddce Move messageId out of outer loop 2019-03-27 16:28:52 +01:00
Bond_009
b647959ec4 Add EnableOutputInSubFolder back 2019-03-27 16:26:33 +01:00
Bond_009
6c0e2e249d Even more duplicate code removed 2019-03-27 16:13:36 +01:00
Bond_009
8ed5d154b7 Remove duplicate code 2019-03-27 16:07:08 +01:00
Bond_009
157a86d0f1 Remove dead code 2019-03-27 12:43:46 +01:00
Bond_009
ca37ca291f More style changes 2019-03-26 23:06:38 +01:00
Bond_009
93e535d3a1 Trying to make sense of the streaming code
Mostly small changes as I was looking through the code.

* async void -> async Task
* Properly implemented dispose methods
* Pass the logstream directly to the JobLogger
* Style fixes
2019-03-26 23:00:14 +01:00
Bond-009
a332092769 Reduce complexity http routes 2019-03-26 19:20:40 +01:00
Bond_009
2696ac5eac Lower the amount of running tasks 2019-03-25 21:33:48 +01:00
Bond-009
6566c91360 Seperate changes from #1023
The unrelated changes from #1023 (and more)
2019-03-25 21:27:03 +01:00
Bond-009
7f42dcc60f Remove more unused stuff 2019-03-08 20:32:14 +01:00
Bond-009
369785c184 Remove usage of depricated 'WebRequest'
Ref: https://docs.microsoft.com/en-us/dotnet/api/system.net.webrequest?view=netframework-4.7.2
2019-03-08 20:17:17 +01:00
663 changed files with 16782 additions and 25001 deletions

View File

@@ -2,7 +2,7 @@ name: $(Date:yyyyMMdd)$(Rev:.r)
variables:
- name: TestProjects
value: 'Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj'
value: 'tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj'
- name: RestoreBuildProjects
value: 'Jellyfin.Server/Jellyfin.Server.csproj'
@@ -16,7 +16,7 @@ jobs:
- job: main_build
displayName: Main Build
pool:
vmImage: ubuntu-16.04
vmImage: ubuntu-latest
strategy:
matrix:
release:
@@ -28,27 +28,43 @@ jobs:
- checkout: self
clean: true
submodules: true
persistCredentials: false
persistCredentials: true
- task: DotNetCoreCLI@2
displayName: Restore
- task: CmdLine@2
displayName: "Check out web"
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
command: restore
projects: '$(RestoreBuildProjects)'
script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
- task: DotNetCoreCLI@2
displayName: Build
- task: CmdLine@2
displayName: "Check out web (PR)"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
inputs:
projects: '$(RestoreBuildProjects)'
arguments: '--configuration $(BuildConfiguration)'
script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
- task: DotNetCoreCLI@2
displayName: Test
- task: NodeTool@0
displayName: 'Install Node.js'
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
command: test
projects: '$(RestoreBuildProjects)'
arguments: '--configuration $(BuildConfiguration)'
enabled: false
versionSpec: '10.x'
- task: CmdLine@2
displayName: "Build Web UI"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
script: yarn install
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
- task: CopyFiles@2
displayName: Copy the web UI
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
contents: '**'
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: false # Optional
- task: DotNetCoreCLI@2
displayName: Publish
@@ -59,45 +75,205 @@ jobs:
arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)'
zipAfterPublish: false
# - task: PublishBuildArtifacts@1
# displayName: 'Publish Artifact'
# inputs:
# PathtoPublish: '$(build.artifactstagingdirectory)'
# artifactName: 'jellyfin-build-$(BuildConfiguration)'
# zipAfterPublish: true
- task: PublishBuildArtifacts@1
- task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Naming'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll'
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll'
artifactName: 'Jellyfin.Naming'
- task: PublishBuildArtifacts@1
- task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Controller'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
artifactName: 'Jellyfin.Controller'
- task: PublishBuildArtifacts@1
- task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Model'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
artifactName: 'Jellyfin.Model'
- task: PublishBuildArtifacts@1
- task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Common'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
artifactName: 'Jellyfin.Common'
- job: main_test
displayName: Main Test
pool:
vmImage: windows-latest
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: false
- task: DotNetCoreCLI@2
displayName: Build
inputs:
command: build
publishWebProjects: false
projects: '$(TestProjects)'
arguments: '--configuration $(BuildConfiguration)'
zipAfterPublish: false
- task: VisualStudioTestPlatformInstaller@1
inputs:
packageFeedSelector: 'nugetOrg' # Options: nugetOrg, customFeed, netShare
versionSelector: 'latestPreRelease' # Required when packageFeedSelector == NugetOrg || PackageFeedSelector == CustomFeed# Options: latestPreRelease, latestStable, specificVersion
- task: VSTest@2
inputs:
testSelector: 'testAssemblies' # Options: testAssemblies, testPlan, testRun
testAssemblyVer2: | # Required when testSelector == TestAssemblies
**\bin\$(BuildConfiguration)\**\*test*.dll
!**\obj\**
!**\xunit.runner.visualstudio.testadapter.dll
!**\xunit.runner.visualstudio.dotnetcore.testadapter.dll
#testPlan: # Required when testSelector == TestPlan
#testSuite: # Required when testSelector == TestPlan
#testConfiguration: # Required when testSelector == TestPlan
#tcmTestRun: '$(test.RunId)' # Optional
searchFolder: '$(System.DefaultWorkingDirectory)'
#testFiltercriteria: # Optional
#runOnlyImpactedTests: False # Optional
#runAllTestsAfterXBuilds: '50' # Optional
#uiTests: false # Optional
#vstestLocationMethod: 'version' # Optional. Options: version, location
#vsTestVersion: 'latest' # Optional. Options: latest, 16.0, 15.0, 14.0, toolsInstaller
#vstestLocation: # Optional
#runSettingsFile: # Optional
#overrideTestrunParameters: # Optional
#pathtoCustomTestAdapters: # Optional
runInParallel: True # Optional
runTestsInIsolation: True # Optional
codeCoverageEnabled: True # Optional
#otherConsoleOptions: # Optional
#distributionBatchType: 'basedOnTestCases' # Optional. Options: basedOnTestCases, basedOnExecutionTime, basedOnAssembly
#batchingBasedOnAgentsOption: 'autoBatchSize' # Optional. Options: autoBatchSize, customBatchSize
#customBatchSizeValue: '10' # Required when distributionBatchType == BasedOnTestCases && BatchingBasedOnAgentsOption == CustomBatchSize
#batchingBasedOnExecutionTimeOption: 'autoBatchSize' # Optional. Options: autoBatchSize, customTimeBatchSize
#customRunTimePerBatchValue: '60' # Required when distributionBatchType == BasedOnExecutionTime && BatchingBasedOnExecutionTimeOption == CustomTimeBatchSize
#dontDistribute: False # Optional
#testRunTitle: # Optional
#platform: # Optional
configuration: 'Debug' # Optional
publishRunAttachments: true # Optional
#diagnosticsEnabled: false # Optional
#collectDumpOn: 'onAbortOnly' # Optional. Options: onAbortOnly, always, never
#rerunFailedTests: False # Optional
#rerunType: 'basedOnTestFailurePercentage' # Optional. Options: basedOnTestFailurePercentage, basedOnTestFailureCount
#rerunFailedThreshold: '30' # Optional
#rerunFailedTestCasesMaxLimit: '5' # Optional
#rerunMaxAttempts: '3' # Optional
# - task: PublishTestResults@2
# inputs:
# testResultsFormat: 'VSTest' # Options: JUnit, NUnit, VSTest, xUnit, cTest
# testResultsFiles: '**/*.trx'
# #searchFolder: '$(System.DefaultWorkingDirectory)' # Optional
# mergeTestResults: true # Optional
# #failTaskOnFailedTests: false # Optional
# #testRunTitle: # Optional
# #buildPlatform: # Optional
# #buildConfiguration: # Optional
# #publishRunAttachments: true # Optional
- job: main_build_win
displayName: Main Build Windows
pool:
vmImage: windows-latest
strategy:
matrix:
release:
BuildConfiguration: Release
maxParallel: 2
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: true
- task: CmdLine@2
displayName: "Check out web"
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
- task: CmdLine@2
displayName: "Check out web (PR)"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
inputs:
script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
- task: NodeTool@0
displayName: 'Install Node.js'
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
versionSpec: '10.x'
- task: CmdLine@2
displayName: "Build Web UI"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
script: yarn install
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
- task: CopyFiles@2
displayName: Copy the web UI
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
contents: '**'
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: false # Optional
- task: CmdLine@2
displayName: Clone the UX repository
inputs:
script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
- task: PowerShell@2
displayName: Build the NSIS Installer
inputs:
targetType: 'filePath' # Optional. Options: filePath, inline
filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath
arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
#script: '# Write your PowerShell commands here.Write-Host Hello World' # Required when targetType == Inline
errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue
#failOnStderr: false # Optional
#ignoreLASTEXITCODE: false # Optional
#pwsh: false # Optional
workingDirectory: $(Build.SourcesDirectory) # Optional
- task: CopyFiles@2
displayName: Copy the NSIS Installer to the artifact directory
inputs:
sourceFolder: $(Build.SourcesDirectory)/deployment/windows/ # Optional
contents: 'jellyfin*.exe'
targetFolder: $(System.ArtifactsDirectory)/setup
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: true # Optional
- task: PublishPipelineArtifact@0
displayName: 'Publish Setup Artifact'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
targetPath: '$(build.artifactstagingdirectory)/setup'
artifactName: 'Jellyfin Server Setup'
- job: dotnet_compat
displayName: Compatibility Check
pool:
vmImage: ubuntu-16.04
vmImage: ubuntu-latest
dependsOn: main_build
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber']) # Only execute if the pullrequest numer is defined. (So not for normal CI builds)
strategy:
@@ -118,45 +294,23 @@ jobs:
steps:
- checkout: none
- task: DownloadBuildArtifacts@0
displayName: Download the Reference Assembly Build Artifact
inputs:
buildType: 'specific' # Options: current, specific
project: $(System.TeamProjectId) # Required when buildType == Specific
pipeline: $(System.DefinitionId) # Required when buildType == Specific, not sure if this will take a name too
#specificBuildWithTriggering: false # Optional
buildVersionToDownload: 'latestFromBranch' # Required when buildType == Specific# Options: latest, latestFromBranch, specific
allowPartiallySucceededBuilds: false # Optional
branchName: '$(System.PullRequest.TargetBranch)' # Required when buildType == Specific && BuildVersionToDownload == LatestFromBranch
#buildId: # Required when buildType == Specific && BuildVersionToDownload == Specific
#tags: # Optional
downloadType: 'single' # Options: single, specific
artifactName: '$(NugetPackageName)'# Required when downloadType == Single
#itemPattern: '**' # Optional
downloadPath: '$(System.ArtifactsDirectory)/current-artifacts'
#parallelizationLimit: '8' # Optional
- task: CopyFiles@2
displayName: Copy Nuget Assembly to current-release folder
inputs:
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts # Optional
contents: '**/*.dll'
targetFolder: $(System.ArtifactsDirectory)/current-release
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: true # Optional
- task: DownloadBuildArtifacts@0
- task: DownloadPipelineArtifact@2
displayName: Download the New Assembly Build Artifact
inputs:
buildType: 'current' # Options: current, specific
allowPartiallySucceededBuilds: false # Optional
downloadType: 'single' # Options: single, specific
artifactName: '$(NugetPackageName)' # Required when downloadType == Single
downloadPath: '$(System.ArtifactsDirectory)/new-artifacts'
source: 'current' # Options: current, specific
#preferTriggeringPipeline: false # Optional
#tags: # Optional
artifact: '$(NugetPackageName)' # Optional
#patterns: '**' # Optional
path: '$(System.ArtifactsDirectory)/new-artifacts'
#project: # Required when source == Specific
#pipeline: # Required when source == Specific
runVersion: 'latest' # Required when source == Specific. Options: latest, latestFromBranch, specific
#runBranch: 'refs/heads/master' # Required when source == Specific && runVersion == LatestFromBranch
#runId: # Required when source == Specific && runVersion == Specific
- task: CopyFiles@2
displayName: Copy Artifact Assembly to new-release folder
displayName: Copy New Assembly to new-release folder
inputs:
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts # Optional
contents: '**/*.dll'
@@ -165,10 +319,35 @@ jobs:
overWrite: true # Optional
flattenFolders: true # Optional
- task: DownloadPipelineArtifact@2
displayName: Download the Reference Assembly Build Artifact
inputs:
source: 'specific' # Options: current, specific
#preferTriggeringPipeline: false # Optional
#tags: # Optional
artifact: '$(NugetPackageName)' # Optional
#patterns: '**' # Optional
path: '$(System.ArtifactsDirectory)/current-artifacts'
project: '$(System.TeamProjectId)' # Required when source == Specific
pipeline: '$(System.DefinitionId)' # Required when source == Specific
runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
runBranch: 'refs/heads/$(System.PullRequest.TargetBranch)' # Required when source == Specific && runVersion == LatestFromBranch
#runId: # Required when source == Specific && runVersion == Specific
- task: CopyFiles@2
displayName: Copy Reference Assembly to current-release folder
inputs:
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts # Optional
contents: '**/*.dll'
targetFolder: $(System.ArtifactsDirectory)/current-release
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: true # Optional
- task: DownloadGitHubRelease@0
displayName: Download ABI compatibility check tool from GitHub
inputs:
connection: Jellyfin GitHub
connection: Jellyfin Release Download
userRepository: EraYaN/dotnet-compatibility
defaultVersionType: 'latest' # Options: latest, specificVersion, specificTag
#version: # Required when defaultVersionType != Latest
@@ -185,7 +364,7 @@ jobs:
- task: CmdLine@2
displayName: Execute ABI compatibility check tool
inputs:
script: 'dotnet tools/CompatibilityCheckerCoreCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName)'
script: 'dotnet tools/CompatibilityCheckerCoreCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines'
workingDirectory: $(System.ArtifactsDirectory) # Optional
#failOnStderr: false # Optional

46
.ci/publish-nightly.yml Normal file
View File

@@ -0,0 +1,46 @@
name: Nightly-$(date:yyyyMMdd).$(rev:r)
variables:
- name: Version
value: '1.0.0'
trigger: none
pr: none
jobs:
- job: publish_artifacts_nightly
displayName: Publish Artifacts Nightly
pool:
vmImage: ubuntu-latest
steps:
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download the Windows Setup Artifact
inputs:
source: 'specific' # Options: current, specific
artifact: 'Jellyfin Server Setup' # Optional
path: '$(System.ArtifactsDirectory)/win-installer'
project: '$(System.TeamProjectId)' # Required when source == Specific
pipelineId: 1 # Required when source == Specific
runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
runBranch: 'refs/heads/master' # Required when source == Specific && runVersion == LatestFromBranch
- task: SSH@0
displayName: 'Create Drop directory'
inputs:
sshEndpoint: 'Jellyfin Build Server'
commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_nightly_azure_upload'
- task: CopyFilesOverSSH@0
displayName: 'Copy the Windows Setup to the Repo'
inputs:
sshEndpoint: 'Jellyfin Build Server'
sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
contents: 'jellyfin_*.exe'
targetFolder: '/srv/incoming/jellyfin_nightly_azure_upload/win-installer'
- task: SSH@0
displayName: 'Clean up SCP symlink'
inputs:
sshEndpoint: 'Jellyfin Build Server'
commands: 'rm -f /srv/incoming/jellyfin_nightly_azure_upload'

48
.ci/publish-release.yml Normal file
View File

@@ -0,0 +1,48 @@
name: Release-$(Version)-$(date:yyyyMMdd).$(rev:r)
variables:
- name: Version
value: '1.0.0'
- name: UsedRunId
value: 0
trigger: none
pr: none
jobs:
- job: publish_artifacts_release
displayName: Publish Artifacts Release
pool:
vmImage: ubuntu-latest
steps:
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download the Windows Setup Artifact
inputs:
source: 'specific' # Options: current, specific
artifact: 'Jellyfin Server Setup' # Optional
path: '$(System.ArtifactsDirectory)/win-installer'
project: '$(System.TeamProjectId)' # Required when source == Specific
pipelineId: 1 # Required when source == Specific
runVersion: 'specific' # Required when source == Specific. Options: latest, latestFromBranch, specific
runId: $(UsedRunId)
- task: SSH@0
displayName: 'Create Drop directory'
inputs:
sshEndpoint: 'Jellyfin Build Server'
commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_release_azure_upload'
- task: CopyFilesOverSSH@0
displayName: 'Copy the Windows Setup to the Repo'
inputs:
sshEndpoint: 'Jellyfin Build Server'
sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
contents: 'jellyfin_*.exe'
targetFolder: '/srv/incoming/jellyfin_release_azure_upload/win-installer'
- task: SSH@0
displayName: 'Clean up SCP symlink'
inputs:
sshEndpoint: 'Jellyfin Build Server'
commands: 'rm -f /srv/incoming/jellyfin_release_azure_upload'

View File

@@ -1,8 +1,59 @@
srpm:
dnf -y install git
git submodule update --init --recursive
cd deployment/fedora-package-x64; \
./create_tarball.sh; \
rpmbuild -bs pkg-src/jellyfin.spec \
--define "_sourcedir $$PWD/pkg-src/" \
--define "_srcrpmdir $(outdir)"
VERSION := $(shell sed -ne '/^Version:/s/.* *//p' \
deployment/fedora-package-x64/pkg-src/jellyfin.spec)
deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz:
curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \
|| curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz \
srpm: deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz
cd deployment/fedora-package-x64; \
SOURCE_DIR=../.. \
WORKDIR="$${PWD}"; \
package_temporary_dir="$${WORKDIR}/pkg-dist-tmp"; \
pkg_src_dir="$${WORKDIR}/pkg-src"; \
GNU_TAR=1; \
tar \
--transform "s,^\.,jellyfin-$(VERSION)," \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
--exclude='**/.vs' \
--exclude='**/.vscode' \
--exclude='deployment' \
--exclude='**/bin' \
--exclude='**/obj' \
--exclude='**/.nuget' \
--exclude='*.deb' \
--exclude='*.rpm' \
-czf "pkg-src/jellyfin-$(VERSION).tar.gz" \
-C $${SOURCE_DIR} ./ || GNU_TAR=0; \
if [ $${GNU_TAR} -eq 0 ]; then \
package_temporary_dir="$$(mktemp -d)"; \
mkdir -p "$${package_temporary_dir}/jellyfin"; \
tar \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
--exclude='**/.vs' \
--exclude='**/.vscode' \
--exclude='deployment' \
--exclude='**/bin' \
--exclude='**/obj' \
--exclude='**/.nuget' \
--exclude='*.deb' \
--exclude='*.rpm' \
-czf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
-C $${SOURCE_DIR} ./; \
mkdir -p "$${package_temporary_dir}/jellyfin-$(VERSION)"; \
tar -xzf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
-C "$${package_temporary_dir}/jellyfin-$(VERSION); \
rm -f "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"; \
tar -czf "$${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-$(VERSION).tar.gz" \
-C "$${package_temporary_dir}" "jellyfin-$(VERSION); \
rm -rf $${package_temporary_dir}; \
fi; \
rpmbuild -bs pkg-src/jellyfin.spec \
--define "_sourcedir $$PWD/pkg-src/" \
--define "_srcrpmdir $(outdir)"

4
.gitattributes vendored
View File

@@ -1 +1,5 @@
* text=auto eol=lf
*.png binary
*.jpg binary
CONTRIBUTORS.md merge=union

View File

@@ -1,20 +0,0 @@
---
name: Enhancement request
about: Suggest an modification to an existing feature
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->

View File

@@ -1,14 +0,0 @@
---
name: Feature request
about: Suggest a new feature
title: ''
labels: feature
assignees: ''
---
**Describe the feature you'd like**
<!-- A clear and concise description of what you want to happen. -->
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->

View File

@@ -0,0 +1,32 @@
---
name: Media playback issue
about: Create a media playback issue report
title: ''
labels: mediaplayback
assignees: ''
---
**Media Info of the file**
<!-- Use the Media Info tool (set to text format, download here: https://mediaarea.net/en/MediaInfo) or copy the info from the web ui for the file with the playback issue. -->
**Logs**
<!-- Please paste any log message from during the playback issue, for example the ffmpeg command line can be very useful. -->
**Stats for Nerds Screenshots**
<!-- If available, add screenshots of the stats for nerds screen to help show the issue problem. -->
**Server System (please complete the following information):**
- OS: [e.g. Docker on Linux, Docker on Windows, Debian, Windows]
- Jellyfin Version: [e.g. 10.0.1]
- Hardware settings & device: [e.g. NVENC on GTX1060, VAAPI on Intel i7 8700K]
- Reverse proxy: [e.g. no, nginx, apache, etc.]
- Other hardware notes: [e.g. Media mounted in CIFS/SMB share, Media mounted from Google Drive]
**Client System (please complete the following information):**
- Device: [e.g. Apple iPhone XS, Xbox One S, LG OLED55C8, Samsung Galaxy Note9, Custom HTPC]
- OS: [e.g. iOS, Android, Windows, macOS]
- Client: [e.g. Web/Browser, webOS, Android, Android TV, Electron]
- Browser (if Web client): [e.g. Firefox, Chrome, Safari]
- Client and Browser Version: [e.g. 10.3.4 and 68.0]

View File

@@ -1,6 +1,6 @@
<!--
Ensure your title is short, descriptive, and in the imperative mood (Fix X, Change Y, instead of Fixed X, Changed Y).
For a good inspiration of what to write in commit messages and PRs please review https://chris.beams.io/posts/git-commit/ and our https://jellyfin.readthedocs.io/en/latest/developer-docs/contributing/ page.
For a good inspiration of what to write in commit messages and PRs please review https://chris.beams.io/posts/git-commit/ and our documentation.
-->
**Changes**

22
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 90
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 14
# Issues with these labels will never be considered stale
exemptLabels:
- regression
- security
- dotnet-3.0-future
- roadmap
- future
- feature
- enhancement
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
Issues go stale after 90d of inactivity. Mark the issue as fresh by adding a comment or commit. Stale issues close after an additional 14d of inactivity.
If this issue is safe to close now please do so.
If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

11
.gitignore vendored
View File

@@ -239,11 +239,6 @@ pip-log.txt
##########
.idea/
##########
# Visual Studio Code
##########
.vscode/
#########################
# Build artifacts
#########################
@@ -268,4 +263,8 @@ jellyfin_version.ini
ci/
# Doxygen
doc/
doc/
# Deployment artifacts
dist
*.exe

4
.gitmodules vendored
View File

@@ -1,4 +0,0 @@
[submodule "MediaBrowser.WebDashboard/jellyfin-web"]
path = MediaBrowser.WebDashboard/jellyfin-web
url = https://github.com/jellyfin/jellyfin-web.git
branch = .

View File

@@ -11,6 +11,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>

View File

@@ -212,7 +212,6 @@ namespace BDInfo
public void Scan()
{
var errorStreamClipFiles = new List<TSStreamClipFile>();
foreach (var streamClipFile in StreamClipFiles.Values)
{
try
@@ -221,7 +220,6 @@ namespace BDInfo
}
catch (Exception ex)
{
errorStreamClipFiles.Add(streamClipFile);
if (StreamClipFileScanError != null)
{
if (StreamClipFileScanError(streamClipFile, ex))
@@ -250,7 +248,6 @@ namespace BDInfo
StreamFiles.Values.CopyTo(streamFiles, 0);
Array.Sort(streamFiles, CompareStreamFiles);
var errorPlaylistFiles = new List<TSPlaylistFile>();
foreach (var playlistFile in PlaylistFiles.Values)
{
try
@@ -259,7 +256,6 @@ namespace BDInfo
}
catch (Exception ex)
{
errorPlaylistFiles.Add(playlistFile);
if (PlaylistFileScanError != null)
{
if (PlaylistFileScanError(playlistFile, ex))
@@ -275,7 +271,6 @@ namespace BDInfo
}
}
var errorStreamFiles = new List<TSStreamFile>();
foreach (var streamFile in streamFiles)
{
try
@@ -296,7 +291,6 @@ namespace BDInfo
}
catch (Exception ex)
{
errorStreamFiles.Add(streamFile);
if (StreamFileScanError != null)
{
if (StreamFileScanError(streamFile, ex))
@@ -431,7 +425,7 @@ namespace BDInfo
{
return 1;
}
else if ((x != null || x.FileInfo != null) && (y == null || y.FileInfo == null))
else if ((x != null && x.FileInfo != null) && (y == null || y.FileInfo == null))
{
return -1;
}
@@ -451,6 +445,5 @@ namespace BDInfo
}
}
}
}
}

View File

@@ -23,7 +23,13 @@
- [fruhnow](https://github.com/fruhnow)
- [Lynxy](https://github.com/Lynxy)
- [fasheng](https://github.com/fasheng)
- [ploughpuff](https://github.com/ploughpuff)
- [ploughpuff](https://github.com/ploughpuff)
- [pjeanjean](https://github.com/pjeanjean)
- [DrPandemic](https://github.com/drpandemic)
- [joern-h](https://github.com/joern-h)
- [Khinenw](https://github.com/HelloWorld017)
- [fhriley](https://github.com/fhriley)
- [nevado](https://github.com/nevado)
# Emby Contributors

View File

@@ -1,30 +1,38 @@
ARG DOTNET_VERSION=2.2
ARG FFMPEG_VERSION=latest
FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=v10.4.3
RUN apk add curl \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
&& yarn install \
&& yarn build \
&& mv dist /dist
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
RUN bash -c "source deployment/common.build.sh && \
build_jellyfin Jellyfin.Server Release linux-x64 /jellyfin"
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
FROM jellyfin/ffmpeg as ffmpeg
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}
# libfontconfig1 is required for Skia
COPY --from=ffmpeg / /
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
# Install dependencies:
# libfontconfig1: needed for Skia
# mesa-va-drivers: needed for VAAPI
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y \
libfontconfig1 \
libfontconfig1 mesa-va-drivers \
&& apt-get clean autoclean \
&& apt-get autoremove \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media
COPY --from=ffmpeg / /
COPY --from=builder /jellyfin /jellyfin
ARG JELLYFIN_WEB_VERSION=10.3.0-rc2
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& rm -rf /jellyfin/jellyfin-web \
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
EXPOSE 8096
VOLUME /cache /config /media

View File

@@ -3,10 +3,15 @@
ARG DOTNET_VERSION=3.0
FROM multiarch/qemu-user-static:x86_64-arm as qemu
FROM alpine as qemu_extract
COPY --from=qemu /usr/bin qemu-arm-static.tar.gz
RUN tar -xzvf qemu-arm-static.tar.gz
FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=v10.4.3
RUN apk add curl \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
&& yarn install \
&& yarn build \
&& mv dist /dist
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
@@ -17,23 +22,19 @@ RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
RUN bash -c "source deployment/common.build.sh && \
build_jellyfin Jellyfin.Server Release linux-arm /jellyfin"
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
FROM multiarch/qemu-user-static:x86_64-arm as qemu
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm32v7
COPY --from=qemu_extract qemu-arm-static /usr/bin
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media
COPY --from=builder /jellyfin /jellyfin
ARG JELLYFIN_WEB_VERSION=10.3.0-rc2
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& rm -rf /jellyfin/jellyfin-web \
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
COPY --from=web-builder /dist /jellyfin/jellyfin-web
EXPOSE 8096
VOLUME /cache /config /media

View File

@@ -3,10 +3,14 @@
ARG DOTNET_VERSION=3.0
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
FROM alpine as qemu_extract
COPY --from=qemu /usr/bin qemu-aarch64-static.tar.gz
RUN tar -xzvf qemu-aarch64-static.tar.gz
FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=v10.4.3
RUN apk add curl \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
&& yarn install \
&& yarn build \
&& mv dist /dist
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
@@ -18,23 +22,19 @@ RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
RUN bash -c "source deployment/common.build.sh && \
build_jellyfin Jellyfin.Server Release linux-arm64 /jellyfin"
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm64v8
COPY --from=qemu_extract qemu-aarch64-static /usr/bin
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media
COPY --from=builder /jellyfin /jellyfin
ARG JELLYFIN_WEB_VERSION=10.3.0-rc2
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& rm -rf /jellyfin/jellyfin-web \
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
COPY --from=web-builder /dist /jellyfin/jellyfin-web
EXPOSE 8096
VOLUME /cache /config /media

5130
Doxyfile

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>

View File

@@ -6,6 +6,7 @@ using System.Text;
using System.Threading.Tasks;
using Emby.Dlna.Main;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Services;
@@ -108,12 +109,13 @@ namespace Emby.Dlna.Api
public class DlnaServerService : IService, IRequiresRequest
{
private readonly IDlnaManager _dlnaManager;
private const string XMLContentType = "text/xml; charset=UTF-8";
private readonly IDlnaManager _dlnaManager;
private readonly IHttpResultFactory _resultFactory;
private readonly IServerConfigurationManager _configurationManager;
public IRequest Request { get; set; }
private IHttpResultFactory _resultFactory;
private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory;
@@ -121,10 +123,14 @@ namespace Emby.Dlna.Api
private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar;
public DlnaServerService(IDlnaManager dlnaManager, IHttpResultFactory httpResultFactory)
public DlnaServerService(
IDlnaManager dlnaManager,
IHttpResultFactory httpResultFactory,
IServerConfigurationManager configurationManager)
{
_dlnaManager = dlnaManager;
_resultFactory = httpResultFactory;
_configurationManager = configurationManager;
}
private string GetHeader(string name)
@@ -205,19 +211,32 @@ namespace Emby.Dlna.Api
var pathInfo = Parse(Request.PathInfo);
var first = pathInfo[0];
string baseUrl = _configurationManager.Configuration.BaseUrl;
// backwards compatibility
// TODO: Work out what this is doing.
if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) ||
string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase) ||
string.Equals(first, "jellyfin", StringComparison.OrdinalIgnoreCase))
if (baseUrl.Length == 0)
{
if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase)
|| string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase))
{
index++;
}
}
else if (string.Equals(first, baseUrl.Remove(0, 1)))
{
index++;
var second = pathInfo[1];
if (string.Equals(second, "mediabrowser", StringComparison.OrdinalIgnoreCase)
|| string.Equals(second, "emby", StringComparison.OrdinalIgnoreCase))
{
index++;
}
}
return pathInfo[index];
}
private List<string> Parse(string pathUri)
private static string[] Parse(string pathUri)
{
var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None);
@@ -231,7 +250,7 @@ namespace Emby.Dlna.Api
var args = pathInfo.Split('/');
return args.Skip(1).ToList();
return args.Skip(1).ToArray();
}
public object Get(GetIcon request)

View File

@@ -289,7 +289,7 @@ namespace Emby.Dlna.ContentDirectory
var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount);
totalCount = childrenResult.TotalRecordCount;
provided = childrenResult.Items.Length;
provided = childrenResult.Items.Count;
foreach (var i in childrenResult.Items)
{
@@ -309,6 +309,7 @@ namespace Emby.Dlna.ContentDirectory
}
}
}
writer.WriteFullEndElement();
//writer.WriteEndDocument();
}
@@ -386,7 +387,7 @@ namespace Emby.Dlna.ContentDirectory
totalCount = childrenResult.TotalRecordCount;
provided = childrenResult.Items.Length;
provided = childrenResult.Items.Count;
var dlnaOptions = _config.GetDlnaConfiguration();
@@ -677,7 +678,7 @@ namespace Emby.Dlna.ContentDirectory
return new QueryResult<ServerItem>
{
Items = list.ToArray(),
Items = list,
TotalRecordCount = list.Count
};
}
@@ -755,7 +756,7 @@ namespace Emby.Dlna.ContentDirectory
return new QueryResult<ServerItem>
{
Items = list.ToArray(),
Items = list,
TotalRecordCount = list.Count
};
}
@@ -860,7 +861,7 @@ namespace Emby.Dlna.ContentDirectory
return new QueryResult<ServerItem>
{
Items = list.ToArray(),
Items = list,
TotalRecordCount = list.Count
};
}

View File

@@ -158,7 +158,7 @@ namespace Emby.Dlna.Didl
AddGeneralProperties(item, null, context, writer, filter);
AddSamsungBookmarkInfo(item, user, writer);
AddSamsungBookmarkInfo(item, user, writer, streamInfo);
// refID?
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
@@ -181,19 +181,6 @@ namespace Emby.Dlna.Didl
writer.WriteFullEndElement();
}
private string GetMimeType(string input)
{
var mime = MimeTypes.GetMimeType(input);
// TODO: Instead of being hard-coded here, this should probably be moved into all of the existing profiles
if (string.Equals(mime, "video/mp2t", StringComparison.OrdinalIgnoreCase))
{
mime = "video/mpeg";
}
return mime;
}
private void AddVideoResource(DlnaOptions options, XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
{
if (streamInfo == null)
@@ -384,7 +371,7 @@ namespace Emby.Dlna.Didl
var filename = url.Substring(0, url.IndexOf('?'));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
? GetMimeType(filename)
? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType;
writer.WriteAttributeString("protocolInfo", string.Format(
@@ -520,7 +507,7 @@ namespace Emby.Dlna.Didl
var filename = url.Substring(0, url.IndexOf('?'));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
? GetMimeType(filename)
? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType;
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container,
@@ -545,17 +532,10 @@ namespace Emby.Dlna.Didl
}
public static bool IsIdRoot(string id)
{
if (string.IsNullOrWhiteSpace(id)
=> string.IsNullOrWhiteSpace(id)
|| string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
// Samsung sometimes uses 1 as root
|| string.Equals(id, "1", StringComparison.OrdinalIgnoreCase))
{
return true;
}
return false;
}
|| string.Equals(id, "1", StringComparison.OrdinalIgnoreCase);
public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null)
{
@@ -601,7 +581,7 @@ namespace Emby.Dlna.Didl
writer.WriteFullEndElement();
}
private void AddSamsungBookmarkInfo(BaseItem item, User user, XmlWriter writer)
private void AddSamsungBookmarkInfo(BaseItem item, User user, XmlWriter writer, StreamInfo streamInfo)
{
if (!item.SupportsPositionTicksResume || item is Folder)
{
@@ -625,10 +605,11 @@ namespace Emby.Dlna.Didl
}
var userdata = _userDataManager.GetUserData(user, item);
var playbackPositionTicks = (streamInfo != null && streamInfo.StartPositionTicks > 0) ? streamInfo.StartPositionTicks : userdata.PlaybackPositionTicks;
if (userdata.PlaybackPositionTicks > 0)
if (playbackPositionTicks > 0)
{
var elementValue = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(userdata.PlaybackPositionTicks).TotalSeconds).ToString(_usCulture));
var elementValue = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds).ToString(_usCulture));
AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value);
}
}
@@ -920,8 +901,6 @@ namespace Emby.Dlna.Didl
}
}
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
if (!_profile.EnableSingleAlbumArtLimit || string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
{
AddImageResElement(item, writer, 4096, 4096, "jpg", "JPEG_LRG");
@@ -930,6 +909,9 @@ namespace Emby.Dlna.Didl
AddImageResElement(item, writer, 4096, 4096, "png", "PNG_LRG");
AddImageResElement(item, writer, 160, 160, "png", "PNG_TN");
}
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
}
private void AddEmbeddedImageAsCover(string name, XmlWriter writer)
@@ -970,7 +952,7 @@ namespace Emby.Dlna.Didl
writer.WriteAttributeString("protocolInfo", string.Format(
"http-get:*:{0}:{1}",
GetMimeType("file." + format),
MimeTypes.GetMimeType("file." + format),
contentFeatures
));
@@ -1101,7 +1083,7 @@ namespace Emby.Dlna.Didl
public static string GetClientId(Guid idValue, StubType? stubType)
{
var id = idValue.ToString("N");
var id = idValue.ToString("N", CultureInfo.InvariantCulture);
if (stubType.HasValue)
{
@@ -1115,7 +1097,7 @@ namespace Emby.Dlna.Didl
{
var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
_serverAddress,
info.ItemId.ToString("N"),
info.ItemId.ToString("N", CultureInfo.InvariantCulture),
info.Type,
info.ImageTag,
format,

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -300,7 +301,7 @@ namespace Emby.Dlna
profile = ReserializeProfile(tempProfile);
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N");
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
@@ -352,7 +353,7 @@ namespace Emby.Dlna
Info = new DeviceProfileInfo
{
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N"),
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
Name = _fileSystem.GetFileNameWithoutExtension(file),
Type = type
}

View File

@@ -14,6 +14,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>

View File

@@ -55,7 +55,7 @@ namespace Emby.Dlna.Eventing
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
{
var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
var id = "uuid:" + Guid.NewGuid().ToString("N");
var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
// Remove logging for now because some devices are sending this very frequently
// TODO re-enable with dlna debug logging setting

View File

@@ -1,4 +1,6 @@
using System;
using System.Net.Sockets;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Emby.Dlna.PlayTo;
@@ -247,7 +249,7 @@ namespace Emby.Dlna.Main
foreach (var address in addresses)
{
if (address.AddressFamily == IpAddressFamily.InterNetworkV6)
if (address.AddressFamily == AddressFamily.InterNetworkV6)
{
// Not support IPv6 right now
continue;
@@ -306,7 +308,7 @@ namespace Emby.Dlna.Main
{
guid = text.GetMD5();
}
return guid.ToString("N");
return guid.ToString("N", CultureInfo.InvariantCulture);
}
private void SetProperies(SsdpDevice device, string fullDeviceType)

View File

@@ -1,5 +1,7 @@
using System;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
@@ -14,7 +16,6 @@ using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Logging;
@@ -141,7 +142,7 @@ namespace Emby.Dlna.PlayTo
return usn;
}
return usn.GetMD5().ToString("N");
return usn.GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
private async Task AddDevice(UpnpDeviceInfo info, string location, CancellationToken cancellationToken)
@@ -156,7 +157,7 @@ namespace Emby.Dlna.PlayTo
}
else
{
uuid = location.GetMD5().ToString("N");
uuid = location.GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion, uuid, null, uri.OriginalString, null);
@@ -172,7 +173,7 @@ namespace Emby.Dlna.PlayTo
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
string serverAddress;
if (info.LocalIpAddress == null || info.LocalIpAddress.Equals(IpAddressInfo.Any) || info.LocalIpAddress.Equals(IpAddressInfo.IPv6Any))
if (info.LocalIpAddress == null || info.LocalIpAddress.Equals(IPAddress.Any) || info.LocalIpAddress.Equals(IPAddress.IPv6Any))
{
serverAddress = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
}

View File

@@ -16,6 +16,8 @@ namespace Emby.Dlna.PlayTo
private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50";
private const string FriendlyName = "Jellyfin";
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IHttpClient _httpClient;
private readonly IServerConfigurationManager _config;
@@ -25,7 +27,8 @@ namespace Emby.Dlna.PlayTo
_config = config;
}
public async Task<XDocument> SendCommandAsync(string baseUrl,
public async Task<XDocument> SendCommandAsync(
string baseUrl,
DeviceService service,
string command,
string postData,
@@ -34,16 +37,21 @@ namespace Emby.Dlna.PlayTo
{
var cancellationToken = CancellationToken.None;
using (var response = await PostSoapDataAsync(NormalizeServiceUrl(baseUrl, service.ControlUrl), "\"" + service.ServiceType + "#" + command + "\"", postData, header, logRequest, cancellationToken)
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
using (var response = await PostSoapDataAsync(
url,
$"\"{service.ServiceType}#{command}\"",
postData,
header,
logRequest,
cancellationToken)
.ConfigureAwait(false))
using (var stream = response.Content)
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
using (var stream = response.Content)
{
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
}
}
return XDocument.Parse(
await reader.ReadToEndAsync().ConfigureAwait(false),
LoadOptions.PreserveWhitespace);
}
}
@@ -61,9 +69,8 @@ namespace Emby.Dlna.PlayTo
return baseUrl + serviceUrl;
}
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public async Task SubscribeAsync(string url,
public async Task SubscribeAsync(
string url,
string ip,
int port,
string localIp,
@@ -76,9 +83,6 @@ namespace Emby.Dlna.PlayTo
UserAgent = USERAGENT,
LogErrorResponseBody = true,
BufferContent = false,
// The periodic requests may keep some devices awake
LogRequestAsDebug = true
};
options.RequestHeaders["HOST"] = ip + ":" + port.ToString(_usCulture);
@@ -101,47 +105,41 @@ namespace Emby.Dlna.PlayTo
LogErrorResponseBody = true,
BufferContent = false,
// The periodic requests may keep some devices awake
LogRequestAsDebug = true,
CancellationToken = cancellationToken
};
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
using (var stream = response.Content)
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
using (var stream = response.Content)
{
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
}
}
return XDocument.Parse(
await reader.ReadToEndAsync().ConfigureAwait(false),
LoadOptions.PreserveWhitespace);
}
}
private Task<HttpResponseInfo> PostSoapDataAsync(string url,
private Task<HttpResponseInfo> PostSoapDataAsync(
string url,
string soapAction,
string postData,
string header,
bool logRequest,
CancellationToken cancellationToken)
{
if (!soapAction.StartsWith("\""))
soapAction = "\"" + soapAction + "\"";
if (soapAction[0] != '\"')
{
soapAction = $"\"{soapAction}\"";
}
var options = new HttpRequestOptions
{
Url = url,
UserAgent = USERAGENT,
LogRequest = logRequest || _config.GetDlnaConfiguration().EnableDebugLog,
LogErrorResponseBody = true,
BufferContent = false,
// The periodic requests may keep some devices awake
LogRequestAsDebug = true,
CancellationToken = cancellationToken
};
@@ -155,7 +153,6 @@ namespace Emby.Dlna.PlayTo
}
options.RequestContentType = "text/xml";
options.AppendCharsetToMimeType = true;
options.RequestContent = postData;
return _httpClient.Post(options);

View File

@@ -9,7 +9,7 @@ namespace Emby.Dlna.Profiles
{
Name = "Dish Hopper-Joey";
ProtocolInfo = "http-get:*:video/mp2t:*,http-get:*:video/MP1S:*,http-get:*:video/mpeg2:*,http-get:*:video/mp4:*,http-get:*:video/x-matroska:*,http-get:*:audio/mpeg:*,http-get:*:audio/mpeg3:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/mp4a-latm:*,http-get:*:image/jpeg:*";
ProtocolInfo = "http-get:*:video/mp2t:*,http-get:*:video/mpeg:*,http-get:*:video/MP1S:*,http-get:*:video/mpeg2:*,http-get:*:video/mp4:*,http-get:*:video/x-matroska:*,http-get:*:audio/mpeg:*,http-get:*:audio/mpeg3:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/mp4a-latm:*,http-get:*:image/jpeg:*";
Identification = new DeviceIdentification
{

View File

@@ -28,7 +28,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
<ProtocolInfo>http-get:*:video/mp2t:*,http-get:*:video/MP1S:*,http-get:*:video/mpeg2:*,http-get:*:video/mp4:*,http-get:*:video/x-matroska:*,http-get:*:audio/mpeg:*,http-get:*:audio/mpeg3:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/mp4a-latm:*,http-get:*:image/jpeg:*</ProtocolInfo>
<ProtocolInfo>http-get:*:video/mp2t:http-get:*:video/mpeg:*,http-get:*:video/MP1S:*,http-get:*:video/mpeg2:*,http-get:*:video/mp4:*,http-get:*:video/x-matroska:*,http-get:*:audio/mpeg:*,http-get:*:audio/mpeg3:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/mp4a-latm:*,http-get:*:image/jpeg:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>

View File

@@ -3,6 +3,8 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
@@ -15,4 +17,9 @@
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
<PropertyGroup>
<!-- We need at least C# 7.1 for the "default literal" feature-->
<LangVersion>latest</LangVersion>
</PropertyGroup>
</Project>

View File

@@ -22,42 +22,47 @@ using Microsoft.Extensions.Logging;
namespace Emby.Drawing
{
/// <summary>
/// Class ImageProcessor
/// Class ImageProcessor.
/// </summary>
public class ImageProcessor : IImageProcessor, IDisposable
{
/// <summary>
/// The us culture
/// </summary>
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
// Increment this when there's a change requiring caches to be invalidated
private const string Version = "3";
/// <summary>
/// Gets the list of currently registered image processors
/// Image processors are specialized metadata providers that run after the normal ones
/// </summary>
/// <value>The image enhancers.</value>
public IImageEnhancer[] ImageEnhancers { get; private set; }
private static readonly HashSet<string> _transparentImageTypes
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
/// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths;
private IImageEncoder _imageEncoder;
private readonly Func<ILibraryManager> _libraryManager;
private readonly Func<IMediaEncoder> _mediaEncoder;
private readonly Dictionary<string, LockInfo> _locks = new Dictionary<string, LockInfo>();
private bool _disposed = false;
/// <summary>
///
/// </summary>
/// <param name="logger"></param>
/// <param name="appPaths"></param>
/// <param name="fileSystem"></param>
/// <param name="imageEncoder"></param>
/// <param name="libraryManager"></param>
/// <param name="mediaEncoder"></param>
public ImageProcessor(
ILoggerFactory loggerFactory,
ILogger<ImageProcessor> logger,
IServerApplicationPaths appPaths,
IFileSystem fileSystem,
IImageEncoder imageEncoder,
Func<ILibraryManager> libraryManager,
Func<IMediaEncoder> mediaEncoder)
{
_logger = loggerFactory.CreateLogger(nameof(ImageProcessor));
_logger = logger;
_fileSystem = fileSystem;
_imageEncoder = imageEncoder;
_libraryManager = libraryManager;
@@ -69,20 +74,11 @@ namespace Emby.Drawing
ImageHelper.ImageProcessor = this;
}
public IImageEncoder ImageEncoder
{
get => _imageEncoder;
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images");
_imageEncoder = value;
}
}
private string EnhancedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "enhanced-images");
/// <inheritdoc />
public IReadOnlyCollection<string> SupportedInputFormats =>
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
@@ -115,18 +111,20 @@ namespace Emby.Drawing
"wbmp"
};
/// <inheritdoc />
public IReadOnlyCollection<IImageEnhancer> ImageEnhancers { get; set; }
/// <inheritdoc />
public bool SupportsImageCollageCreation => _imageEncoder.SupportsImageCollageCreation;
private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images");
private string EnhancedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "enhanced-images");
public void AddParts(IEnumerable<IImageEnhancer> enhancers)
/// <inheritdoc />
public IImageEncoder ImageEncoder
{
ImageEnhancers = enhancers.ToArray();
get => _imageEncoder;
set => _imageEncoder = value ?? throw new ArgumentNullException(nameof(value));
}
/// <inheritdoc />
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
{
var file = await ProcessImage(options).ConfigureAwait(false);
@@ -137,15 +135,15 @@ namespace Emby.Drawing
}
}
/// <inheritdoc />
public IReadOnlyCollection<ImageFormat> GetSupportedImageOutputFormats()
=> _imageEncoder.SupportedOutputFormats;
private static readonly HashSet<string> TransparentImageTypes
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
/// <inheritdoc />
public bool SupportsTransparency(string path)
=> TransparentImageTypes.Contains(Path.GetExtension(path));
=> _transparentImageTypes.Contains(Path.GetExtension(path));
/// <inheritdoc />
public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
{
if (options == null)
@@ -187,9 +185,9 @@ namespace Emby.Drawing
}
dateModified = supportedImageInfo.dateModified;
bool requiresTransparency = TransparentImageTypes.Contains(Path.GetExtension(originalImagePath));
bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath));
if (options.Enhancers.Length > 0)
if (options.Enhancers.Count > 0)
{
if (item == null)
{
@@ -279,7 +277,7 @@ namespace Emby.Drawing
}
}
private ImageFormat GetOutputFormat(ImageFormat[] clientSupportedFormats, bool requiresTransparency)
private ImageFormat GetOutputFormat(IReadOnlyCollection<ImageFormat> clientSupportedFormats, bool requiresTransparency)
{
var serverFormats = GetSupportedImageOutputFormats();
@@ -320,11 +318,6 @@ namespace Emby.Drawing
}
}
/// <summary>
/// Increment this when there's a change requiring caches to be invalidated
/// </summary>
private const string Version = "3";
/// <summary>
/// Gets the cache file path based on a set of parameters
/// </summary>
@@ -372,9 +365,11 @@ namespace Emby.Drawing
return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLowerInvariant());
}
/// <inheritdoc />
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info)
=> GetImageDimensions(item, info, true);
/// <inheritdoc />
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem)
{
int width = info.Width;
@@ -400,26 +395,19 @@ namespace Emby.Drawing
return size;
}
/// <summary>
/// Gets the size of the image.
/// </summary>
/// <inheritdoc />
public ImageDimensions GetImageDimensions(string path)
=> _imageEncoder.GetImageSize(path);
/// <summary>
/// Gets the image cache tag.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="image">The image.</param>
/// <returns>Guid.</returns>
/// <exception cref="ArgumentNullException">item</exception>
/// <inheritdoc />
public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
{
var supportedEnhancers = GetSupportedEnhancers(item, image.Type);
var supportedEnhancers = GetSupportedEnhancers(item, image.Type).ToArray();
return GetImageCacheTag(item, image, supportedEnhancers);
}
/// <inheritdoc />
public string GetImageCacheTag(BaseItem item, ChapterInfo chapter)
{
try
@@ -437,31 +425,24 @@ namespace Emby.Drawing
}
}
/// <summary>
/// Gets the image cache tag.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="image">The image.</param>
/// <param name="imageEnhancers">The image enhancers.</param>
/// <returns>Guid.</returns>
/// <exception cref="ArgumentNullException">item</exception>
public string GetImageCacheTag(BaseItem item, ItemImageInfo image, IImageEnhancer[] imageEnhancers)
/// <inheritdoc />
public string GetImageCacheTag(BaseItem item, ItemImageInfo image, IReadOnlyCollection<IImageEnhancer> imageEnhancers)
{
string originalImagePath = image.Path;
DateTime dateModified = image.DateModified;
ImageType imageType = image.Type;
// Optimization
if (imageEnhancers.Length == 0)
if (imageEnhancers.Count == 0)
{
return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N");
return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
// Cache name is created with supported enhancers combined with the last config change so we pick up new config changes
var cacheKeys = imageEnhancers.Select(i => i.GetConfigurationCacheKey(item, imageType)).ToList();
cacheKeys.Add(originalImagePath + dateModified.Ticks);
return string.Join("|", cacheKeys).GetMD5().ToString("N");
return string.Join("|", cacheKeys).GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
@@ -480,7 +461,7 @@ namespace Emby.Drawing
{
try
{
string filename = (originalImagePath + dateModified.Ticks.ToString(UsCulture)).GetMD5().ToString("N");
string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
string cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png";
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
@@ -507,16 +488,10 @@ namespace Emby.Drawing
return (originalImagePath, dateModified);
}
/// <summary>
/// Gets the enhanced image.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <returns>Task{System.String}.</returns>
/// <inheritdoc />
public async Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex)
{
var enhancers = GetSupportedEnhancers(item, imageType);
var enhancers = GetSupportedEnhancers(item, imageType).ToArray();
ItemImageInfo imageInfo = item.GetImageInfo(imageType, imageIndex);
@@ -532,7 +507,7 @@ namespace Emby.Drawing
bool inputImageSupportsTransparency,
BaseItem item,
int imageIndex,
IImageEnhancer[] enhancers,
IReadOnlyCollection<IImageEnhancer> enhancers,
CancellationToken cancellationToken)
{
var originalImagePath = image.Path;
@@ -573,6 +548,7 @@ namespace Emby.Drawing
/// <param name="imageIndex">Index of the image.</param>
/// <param name="supportedEnhancers">The supported enhancers.</param>
/// <param name="cacheGuid">The cache unique identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;System.String&gt;.</returns>
/// <exception cref="ArgumentNullException">
/// originalImagePath
@@ -584,9 +560,9 @@ namespace Emby.Drawing
BaseItem item,
ImageType imageType,
int imageIndex,
IImageEnhancer[] supportedEnhancers,
IReadOnlyCollection<IImageEnhancer> supportedEnhancers,
string cacheGuid,
CancellationToken cancellationToken)
CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(originalImagePath))
{
@@ -680,6 +656,7 @@ namespace Emby.Drawing
{
throw new ArgumentNullException(nameof(path));
}
if (string.IsNullOrEmpty(uniqueName))
{
throw new ArgumentNullException(nameof(uniqueName));
@@ -722,6 +699,7 @@ namespace Emby.Drawing
return Path.Combine(path, prefix, filename);
}
/// <inheritdoc />
public void CreateImageCollage(ImageCollageOptions options)
{
_logger.LogInformation("Creating image collage and saving to {Path}", options.OutputPath);
@@ -731,38 +709,25 @@ namespace Emby.Drawing
_logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath);
}
public IImageEnhancer[] GetSupportedEnhancers(BaseItem item, ImageType imageType)
/// <inheritdoc />
public IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType)
{
List<IImageEnhancer> list = null;
foreach (var i in ImageEnhancers)
{
try
if (i.Supports(item, imageType))
{
if (i.Supports(item, imageType))
{
if (list == null)
{
list = new List<IImageEnhancer>();
}
list.Add(i);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in image enhancer: {0}", i.GetType().Name);
yield return i;
}
}
return list == null ? Array.Empty<IImageEnhancer>() : list.ToArray();
}
private Dictionary<string, LockInfo> _locks = new Dictionary<string, LockInfo>();
private class LockInfo
{
public SemaphoreSlim Lock = new SemaphoreSlim(1, 1);
public int Count = 1;
}
private LockInfo GetLock(string key)
{
lock (_locks)
@@ -795,7 +760,7 @@ namespace Emby.Drawing
}
}
private bool _disposed;
/// <inheritdoc />
public void Dispose()
{
_disposed = true;

View File

@@ -5,38 +5,42 @@ using MediaBrowser.Model.Drawing;
namespace Emby.Drawing
{
/// <summary>
/// A fallback implementation of <see cref="IImageEncoder" />.
/// </summary>
public class NullImageEncoder : IImageEncoder
{
/// <inheritdoc />
public IReadOnlyCollection<string> SupportedInputFormats
=> new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "png", "jpeg", "jpg" };
/// <inheritdoc />
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
=> new HashSet<ImageFormat>() { ImageFormat.Jpg, ImageFormat.Png };
public void CropWhiteSpace(string inputPath, string outputPath)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public string Name => "Null Image Encoder";
/// <inheritdoc />
public bool SupportsImageCollageCreation => false;
/// <inheritdoc />
public bool SupportsImageEncoding => false;
/// <inheritdoc />
public ImageDimensions GetImageSize(string path)
=> throw new NotImplementedException();
/// <inheritdoc />
public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public void CreateImageCollage(ImageCollageOptions options)
{
throw new NotImplementedException();
}
public string Name => "Null Image Encoder";
public bool SupportsImageCollageCreation => false;
public bool SupportsImageEncoding => false;
public ImageDimensions GetImageSize(string path)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,108 +0,0 @@
# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
[Bb]in/
[Oo]bj/
# mstest test results
TestResults
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
x64/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.log
*.vspscc
*.vssscc
.builds
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*
# NCrunch
*.ncrunch*
.*crunch*.local.xml
# Installshield output folder
[Ee]xpress
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish
# Publish Web Output
*.Publish.xml
# NuGet Packages Directory
packages
# Windows Azure Build Output
csx
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
[Bb]in
[Oo]bj
sql
TestResults
[Tt]est[Rr]esult*
*.Cache
ClientBin
[Ss]tyle[Cc]op.*
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML

View File

@@ -1,25 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2009
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IsoMounter", "IsoMounter\IsoMounter.csproj", "{B94C929C-6552-4620-9BE5-422DD9A151BA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B94C929C-6552-4620-9BE5-422DD9A151BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B94C929C-6552-4620-9BE5-422DD9A151BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B94C929C-6552-4620-9BE5-422DD9A151BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B94C929C-6552-4620-9BE5-422DD9A151BA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C0E8EAD1-E4D7-44CD-B801-03BD12F30B1B}
EndGlobalSection
EndGlobal

View File

@@ -1,8 +0,0 @@
using MediaBrowser.Model.Plugins;
namespace IsoMounter.Configuration
{
public class PluginConfiguration : BasePluginConfiguration
{
}
}

View File

@@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<Compile Include="..\..\SharedVersion.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
</Project>

View File

@@ -1,477 +0,0 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
namespace IsoMounter
{
public class LinuxIsoManager : IIsoMounter
{
[DllImport("libc", SetLastError = true)]
static extern uint getuid();
#region Private Fields
private readonly bool ExecutablesAvailable;
private readonly ILogger _logger;
private readonly string MountCommand;
private readonly string MountPointRoot;
private readonly IProcessFactory ProcessFactory;
private readonly string SudoCommand;
private readonly string UmountCommand;
#endregion
#region Constructor(s)
public LinuxIsoManager(ILogger logger, IProcessFactory processFactory)
{
_logger = logger;
ProcessFactory = processFactory;
MountPointRoot = Path.DirectorySeparatorChar + "tmp" + Path.DirectorySeparatorChar + "Emby";
_logger.LogDebug(
"[{0}] System PATH is currently set to [{1}].",
Name,
Environment.GetEnvironmentVariable("PATH") ?? ""
);
_logger.LogDebug(
"[{0}] System path separator is [{1}].",
Name,
Path.PathSeparator
);
_logger.LogDebug(
"[{0}] Mount point root is [{1}].",
Name,
MountPointRoot
);
//
// Get the location of the executables we need to support mounting/unmounting ISO images.
//
SudoCommand = GetFullPathForExecutable("sudo");
_logger.LogInformation(
"[{0}] Using version of [sudo] located at [{1}].",
Name,
SudoCommand
);
MountCommand = GetFullPathForExecutable("mount");
_logger.LogInformation(
"[{0}] Using version of [mount] located at [{1}].",
Name,
MountCommand
);
UmountCommand = GetFullPathForExecutable("umount");
_logger.LogInformation(
"[{0}] Using version of [umount] located at [{1}].",
Name,
UmountCommand
);
if (!string.IsNullOrEmpty(SudoCommand) && !string.IsNullOrEmpty(MountCommand) && !string.IsNullOrEmpty(UmountCommand))
{
ExecutablesAvailable = true;
}
else
{
ExecutablesAvailable = false;
}
}
#endregion
#region Interface Implementation for IIsoMounter
public bool IsInstalled => true;
public string Name => "LinuxMount";
public bool RequiresInstallation => false;
public bool CanMount(string path)
{
if (OperatingSystem.Id != OperatingSystemId.Linux)
{
return false;
}
_logger.LogInformation(
"[{0}] Checking we can attempt to mount [{1}], Extension = [{2}], Operating System = [{3}], Executables Available = [{4}].",
Name,
path,
Path.GetExtension(path),
OperatingSystem.Name,
ExecutablesAvailable
);
if (ExecutablesAvailable)
{
return string.Equals(Path.GetExtension(path), ".iso", StringComparison.OrdinalIgnoreCase);
}
else
{
return false;
}
}
public Task Install(CancellationToken cancellationToken)
{
return Task.FromResult(false);
}
public Task<IIsoMount> Mount(string isoPath, CancellationToken cancellationToken)
{
if (MountISO(isoPath, out LinuxMount mountedISO))
{
return Task.FromResult<IIsoMount>(mountedISO);
}
else
{
throw new IOException(string.Format(
"An error occurred trying to mount image [$0].",
isoPath
));
}
}
#endregion
#region Interface Implementation for IDisposable
// Flag: Has Dispose already been called?
private bool disposed = false;
public void Dispose()
{
// Dispose of unmanaged resources.
Dispose(true);
// Suppress finalization.
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposed)
{
return;
}
_logger.LogInformation(
"[{0}] Disposing [{1}].",
Name,
disposing
);
if (disposing)
{
//
// Free managed objects here.
//
}
//
// Free any unmanaged objects here.
//
disposed = true;
}
#endregion
#region Private Methods
private string GetFullPathForExecutable(string name)
{
foreach (string test in (Environment.GetEnvironmentVariable("PATH") ?? "").Split(Path.PathSeparator))
{
string path = test.Trim();
if (!string.IsNullOrEmpty(path) && File.Exists(path = Path.Combine(path, name)))
{
return Path.GetFullPath(path);
}
}
return string.Empty;
}
private uint GetUID()
{
var uid = getuid();
_logger.LogDebug(
"[{0}] GetUserId() returned [{2}].",
Name,
uid
);
return uid;
}
private bool ExecuteCommand(string cmdFilename, string cmdArguments)
{
bool processFailed = false;
var process = ProcessFactory.Create(
new ProcessOptions
{
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
FileName = cmdFilename,
Arguments = cmdArguments,
IsHidden = true,
ErrorDialog = false,
EnableRaisingEvents = true
}
);
try
{
process.Start();
//StreamReader outputReader = process.StandardOutput.;
//StreamReader errorReader = process.StandardError;
_logger.LogDebug(
"[{Name}] Standard output from process is [{Error}].",
Name,
process.StandardOutput.ReadToEnd()
);
_logger.LogDebug(
"[{Name}] Standard error from process is [{Error}].",
Name,
process.StandardError.ReadToEnd()
);
}
catch (Exception ex)
{
processFailed = true;
_logger.LogDebug(ex, "[{Name}] Unhandled exception executing command.", Name);
}
if (!processFailed && process.ExitCode == 0)
{
return true;
}
else
{
return false;
}
}
private bool MountISO(string isoPath, out LinuxMount mountedISO)
{
string cmdArguments;
string cmdFilename;
string mountPoint = Path.Combine(MountPointRoot, Guid.NewGuid().ToString());
if (!string.IsNullOrEmpty(isoPath))
{
_logger.LogInformation(
"[{Name}] Attempting to mount [{Path}].",
Name,
isoPath
);
_logger.LogDebug(
"[{Name}] ISO will be mounted at [{Path}].",
Name,
mountPoint
);
}
else
{
throw new ArgumentNullException(nameof(isoPath));
}
try
{
Directory.CreateDirectory(mountPoint);
}
catch (UnauthorizedAccessException)
{
throw new IOException("Unable to create mount point(Permission denied) for " + isoPath);
}
catch (Exception)
{
throw new IOException("Unable to create mount point for " + isoPath);
}
if (GetUID() == 0)
{
cmdFilename = MountCommand;
cmdArguments = string.Format("\"{0}\" \"{1}\"", isoPath, mountPoint);
}
else
{
cmdFilename = SudoCommand;
cmdArguments = string.Format("\"{0}\" \"{1}\" \"{2}\"", MountCommand, isoPath, mountPoint);
}
_logger.LogDebug(
"[{0}] Mount command [{1}], mount arguments [{2}].",
Name,
cmdFilename,
cmdArguments
);
if (ExecuteCommand(cmdFilename, cmdArguments))
{
_logger.LogInformation(
"[{0}] ISO mount completed successfully.",
Name
);
mountedISO = new LinuxMount(this, isoPath, mountPoint);
}
else
{
_logger.LogInformation(
"[{0}] ISO mount completed with errors.",
Name
);
try
{
Directory.Delete(mountPoint, false);
}
catch (Exception ex)
{
_logger.LogInformation(ex, "[{Name}] Unhandled exception removing mount point.", Name);
}
mountedISO = null;
}
return mountedISO != null;
}
private void UnmountISO(LinuxMount mount)
{
string cmdArguments;
string cmdFilename;
if (mount != null)
{
_logger.LogInformation(
"[{0}] Attempting to unmount ISO [{1}] mounted on [{2}].",
Name,
mount.IsoPath,
mount.MountedPath
);
}
else
{
throw new ArgumentNullException(nameof(mount));
}
if (GetUID() == 0)
{
cmdFilename = UmountCommand;
cmdArguments = string.Format("\"{0}\"", mount.MountedPath);
}
else
{
cmdFilename = SudoCommand;
cmdArguments = string.Format("\"{0}\" \"{1}\"", UmountCommand, mount.MountedPath);
}
_logger.LogDebug(
"[{0}] Umount command [{1}], umount arguments [{2}].",
Name,
cmdFilename,
cmdArguments
);
if (ExecuteCommand(cmdFilename, cmdArguments))
{
_logger.LogInformation(
"[{0}] ISO unmount completed successfully.",
Name
);
}
else
{
_logger.LogInformation(
"[{0}] ISO unmount completed with errors.",
Name
);
}
try
{
Directory.Delete(mount.MountedPath, false);
}
catch (Exception ex)
{
_logger.LogInformation(ex, "[{Name}] Unhandled exception removing mount point.", Name);
}
}
#endregion
#region Internal Methods
internal void OnUnmount(LinuxMount mount)
{
UnmountISO(mount);
}
#endregion
}
}

View File

@@ -1,83 +0,0 @@
using System;
using MediaBrowser.Model.IO;
namespace IsoMounter
{
internal class LinuxMount : IIsoMount
{
#region Private Fields
private readonly LinuxIsoManager linuxIsoManager;
#endregion
#region Constructor(s)
internal LinuxMount(LinuxIsoManager isoManager, string isoPath, string mountFolder)
{
linuxIsoManager = isoManager;
IsoPath = isoPath;
MountedPath = mountFolder;
}
#endregion
#region Interface Implementation for IDisposable
// Flag: Has Dispose already been called?
private bool disposed = false;
public void Dispose()
{
// Dispose of unmanaged resources.
Dispose(true);
// Suppress finalization.
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposed)
{
return;
}
if (disposing)
{
//
// Free managed objects here.
//
linuxIsoManager.OnUnmount(this);
}
//
// Free any unmanaged objects here.
//
disposed = true;
}
#endregion
#region Interface Implementation for IIsoMount
public string IsoPath { get; private set; }
public string MountedPath { get; private set; }
#endregion
}
}

View File

@@ -1,30 +0,0 @@
using System;
using IsoMounter.Configuration;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Serialization;
namespace IsoMounter
{
public class Plugin : BasePlugin<PluginConfiguration>
{
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer)
{
}
private Guid _id = new Guid("4682DD4C-A675-4F1B-8E7C-79ADF137A8F8");
public override Guid Id => _id;
/// <summary>
/// Gets the name of the plugin
/// </summary>
/// <value>The name.</value>
public override string Name => "Iso Mounter";
/// <summary>
/// Gets the description.
/// </summary>
/// <value>The description.</value>
public override string Description => "Mount and stream ISO contents";
}
}

View File

@@ -1,21 +0,0 @@
using System.Reflection;
using System.Resources;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("IsoMounter")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Jellyfin Project")]
[assembly: AssemblyProduct("Jellyfin Server")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

View File

@@ -1,339 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{description}
Copyright (C) {year} {fullname}
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
{signature of Ty Coon}, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@@ -1,14 +0,0 @@
# MediaBrowser.IsoMounting.Linux
This implements two core interfaces, IIsoManager, and IIsoMount.
### IIsoManager
The manager class can be used to create a mount, and also determine if the mounter is capable of mounting a given file.
### IIsoMount
IIsoMount then represents a mount instance, which will be unmounted on disposal.
***
This Linux version use sudo, mount and umount.
You need to add this to your sudo file via visudo(change the username):
Defaults:jsmith !requiretty
jsmith ALL=(root) NOPASSWD: /bin/mount
jsmith ALL=(root) NOPASSWD: /bin/umount

View File

@@ -33,27 +33,29 @@ namespace Emby.Naming.Audio
// Normalize
// Remove whitespace
filename = filename.Replace("-", " ");
filename = filename.Replace(".", " ");
filename = filename.Replace("(", " ");
filename = filename.Replace(")", " ");
filename = filename.Replace('-', ' ');
filename = filename.Replace('.', ' ');
filename = filename.Replace('(', ' ');
filename = filename.Replace(')', ' ');
filename = Regex.Replace(filename, @"\s+", " ");
filename = filename.TrimStart();
foreach (var prefix in _options.AlbumStackingPrefixes)
{
if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) == 0)
if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) != 0)
{
var tmp = filename.Substring(prefix.Length);
continue;
}
tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty;
var tmp = filename.Substring(prefix.Length);
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
{
result.IsMultiPart = true;
break;
}
tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty;
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
{
result.IsMultiPart = true;
break;
}
}

View File

@@ -7,11 +7,13 @@ namespace Emby.Naming.Audio
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the part.
/// </summary>
/// <value>The part.</value>
public string Part { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is multi part.
/// </summary>

View File

@@ -12,35 +12,56 @@ namespace Emby.Naming.AudioBook
/// </summary>
/// <value>The path.</value>
public string Path { get; set; }
/// <summary>
/// Gets or sets the container.
/// </summary>
/// <value>The container.</value>
public string Container { get; set; }
/// <summary>
/// Gets or sets the part number.
/// </summary>
/// <value>The part number.</value>
public int? PartNumber { get; set; }
/// <summary>
/// Gets or sets the chapter number.
/// </summary>
/// <value>The chapter number.</value>
public int? ChapterNumber { get; set; }
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
public bool IsDirectory { get; set; }
/// <inheritdoc/>
public int CompareTo(AudioBookFileInfo other)
{
if (ReferenceEquals(this, other)) return 0;
if (ReferenceEquals(null, other)) return 1;
if (ReferenceEquals(this, other))
{
return 0;
}
if (ReferenceEquals(null, other))
{
return 1;
}
var chapterNumberComparison = Nullable.Compare(ChapterNumber, other.ChapterNumber);
if (chapterNumberComparison != 0) return chapterNumberComparison;
if (chapterNumberComparison != 0)
{
return chapterNumberComparison;
}
var partNumberComparison = Nullable.Compare(PartNumber, other.PartNumber);
if (partNumberComparison != 0) return partNumberComparison;
if (partNumberComparison != 0)
{
return partNumberComparison;
}
return string.Compare(Path, other.Path, StringComparison.Ordinal);
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
@@ -14,14 +15,13 @@ namespace Emby.Naming.AudioBook
_options = options;
}
public AudioBookFilePathParserResult Parse(string path, bool IsDirectory)
public AudioBookFilePathParserResult Parse(string path)
{
var result = Parse(path);
return !result.Success ? new AudioBookFilePathParserResult() : result;
}
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
private AudioBookFilePathParserResult Parse(string path)
{
var result = new AudioBookFilePathParserResult();
var fileName = Path.GetFileNameWithoutExtension(path);
foreach (var expression in _options.AudioBookPartsExpressions)
@@ -40,6 +40,7 @@ namespace Emby.Naming.AudioBook
}
}
}
if (!result.PartNumber.HasValue)
{
var value = match.Groups["part"];

View File

@@ -3,7 +3,9 @@ namespace Emby.Naming.AudioBook
public class AudioBookFilePathParserResult
{
public int? PartNumber { get; set; }
public int? ChapterNumber { get; set; }
public bool Success { get; set; }
}
}

View File

@@ -7,33 +7,40 @@ namespace Emby.Naming.AudioBook
/// </summary>
public class AudioBookInfo
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
public int? Year { get; set; }
/// <summary>
/// Gets or sets the files.
/// </summary>
/// <value>The files.</value>
public List<AudioBookFileInfo> Files { get; set; }
/// <summary>
/// Gets or sets the extras.
/// </summary>
/// <value>The extras.</value>
public List<AudioBookFileInfo> Extras { get; set; }
/// <summary>
/// Gets or sets the alternate versions.
/// </summary>
/// <value>The alternate versions.</value>
public List<AudioBookFileInfo> AlternateVersions { get; set; }
public AudioBookInfo()
{
Files = new List<AudioBookFileInfo>();
Extras = new List<AudioBookFileInfo>();
AlternateVersions = new List<AudioBookFileInfo>();
}
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the year.
/// </summary>
public int? Year { get; set; }
/// <summary>
/// Gets or sets the files.
/// </summary>
/// <value>The files.</value>
public List<AudioBookFileInfo> Files { get; set; }
/// <summary>
/// Gets or sets the extras.
/// </summary>
/// <value>The extras.</value>
public List<AudioBookFileInfo> Extras { get; set; }
/// <summary>
/// Gets or sets the alternate versions.
/// </summary>
/// <value>The alternate versions.</value>
public List<AudioBookFileInfo> AlternateVersions { get; set; }
}
}

View File

@@ -15,7 +15,7 @@ namespace Emby.Naming.AudioBook
_options = options;
}
public IEnumerable<AudioBookInfo> Resolve(List<FileSystemMetadata> files)
public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files)
{
var audioBookResolver = new AudioBookResolver(_options);

View File

@@ -24,19 +24,21 @@ namespace Emby.Naming.AudioBook
return Resolve(path, true);
}
public AudioBookFileInfo Resolve(string path, bool IsDirectory = false)
public AudioBookFileInfo Resolve(string path, bool isDirectory = false)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException(nameof(path));
}
if (IsDirectory) // TODO
// TODO
if (isDirectory)
{
return null;
}
var extension = Path.GetExtension(path);
// Check supported extensions
if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
@@ -45,8 +47,7 @@ namespace Emby.Naming.AudioBook
var container = extension.TrimStart('.');
var parsingResult = new AudioBookFilePathParser(_options)
.Parse(path, IsDirectory);
var parsingResult = new AudioBookFilePathParser(_options).Parse(path);
return new AudioBookFileInfo
{
@@ -54,7 +55,7 @@ namespace Emby.Naming.AudioBook
Container = container,
PartNumber = parsingResult.PartNumber,
ChapterNumber = parsingResult.ChapterNumber,
IsDirectory = IsDirectory
IsDirectory = isDirectory
};
}
}

View File

@@ -6,17 +6,28 @@ namespace Emby.Naming.Common
public class EpisodeExpression
{
private string _expression;
public string Expression { get => _expression;
set { _expression = value; _regex = null; } }
private Regex _regex;
public string Expression
{
get => _expression;
set
{
_expression = value;
_regex = null;
}
}
public bool IsByDate { get; set; }
public bool IsOptimistic { get; set; }
public bool IsNamed { get; set; }
public bool SupportsAbsoluteEpisodeNumbers { get; set; }
public string[] DateTimeFormats { get; set; }
private Regex _regex;
public Regex Regex => _regex ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled));
public EpisodeExpression(string expression, bool byDate)

View File

@@ -6,10 +6,12 @@ namespace Emby.Naming.Common
/// The audio
/// </summary>
Audio = 0,
/// <summary>
/// The photo
/// </summary>
Photo = 1,
/// <summary>
/// The video
/// </summary>

View File

@@ -8,19 +8,25 @@ namespace Emby.Naming.Common
public class NamingOptions
{
public string[] AudioFileExtensions { get; set; }
public string[] AlbumStackingPrefixes { get; set; }
public string[] SubtitleFileExtensions { get; set; }
public char[] SubtitleFlagDelimiters { get; set; }
public string[] SubtitleForcedFlags { get; set; }
public string[] SubtitleDefaultFlags { get; set; }
public EpisodeExpression[] EpisodeExpressions { get; set; }
public string[] EpisodeWithoutSeasonExpressions { get; set; }
public string[] EpisodeMultiPartExpressions { get; set; }
public string[] VideoFileExtensions { get; set; }
public string[] StubFileExtensions { get; set; }
public string[] AudioBookPartsExpressions { get; set; }
@@ -28,12 +34,14 @@ namespace Emby.Naming.Common
public StubTypeRule[] StubTypes { get; set; }
public char[] VideoFlagDelimiters { get; set; }
public Format3DRule[] Format3DRules { get; set; }
public string[] VideoFileStackingExpressions { get; set; }
public string[] CleanDateTimes { get; set; }
public string[] CleanStrings { get; set; }
public string[] CleanDateTimes { get; set; }
public string[] CleanStrings { get; set; }
public EpisodeExpression[] MultipleEpisodeExpressions { get; set; }
@@ -41,7 +49,7 @@ namespace Emby.Naming.Common
public NamingOptions()
{
VideoFileExtensions = new string[]
VideoFileExtensions = new[]
{
".m4v",
".3gp",
@@ -106,53 +114,53 @@ namespace Emby.Naming.Common
{
new StubTypeRule
{
StubType = "dvd",
Token = "dvd"
StubType = "dvd",
Token = "dvd"
},
new StubTypeRule
{
StubType = "hddvd",
Token = "hddvd"
StubType = "hddvd",
Token = "hddvd"
},
new StubTypeRule
{
StubType = "bluray",
Token = "bluray"
StubType = "bluray",
Token = "bluray"
},
new StubTypeRule
{
StubType = "bluray",
Token = "brrip"
StubType = "bluray",
Token = "brrip"
},
new StubTypeRule
{
StubType = "bluray",
Token = "bd25"
StubType = "bluray",
Token = "bd25"
},
new StubTypeRule
{
StubType = "bluray",
Token = "bd50"
StubType = "bluray",
Token = "bd50"
},
new StubTypeRule
{
StubType = "vhs",
Token = "vhs"
StubType = "vhs",
Token = "vhs"
},
new StubTypeRule
{
StubType = "tv",
Token = "HDTV"
StubType = "tv",
Token = "HDTV"
},
new StubTypeRule
{
StubType = "tv",
Token = "PDTV"
StubType = "tv",
Token = "PDTV"
},
new StubTypeRule
{
StubType = "tv",
Token = "DSR"
StubType = "tv",
Token = "DSR"
}
};
@@ -286,7 +294,7 @@ namespace Emby.Naming.Common
new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"),
new EpisodeExpression("([0-9]{4})[\\.-]([0-9]{2})[\\.-]([0-9]{2})", true)
{
DateTimeFormats = new []
DateTimeFormats = new[]
{
"yyyy.MM.dd",
"yyyy-MM-dd",
@@ -295,7 +303,7 @@ namespace Emby.Naming.Common
},
new EpisodeExpression("([0-9]{2})[\\.-]([0-9]{2})[\\.-]([0-9]{4})", true)
{
DateTimeFormats = new []
DateTimeFormats = new[]
{
"dd.MM.yyyy",
"dd-MM-yyyy",
@@ -303,6 +311,14 @@ namespace Emby.Naming.Common
}
},
// This isn't a Kodi naming rule, but the expression below causes false positives,
// so we make sure this one gets tested first.
// "Foo Bar 889"
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$")
{
IsNamed = true
},
new EpisodeExpression("[\\\\/\\._ \\[\\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$")
{
SupportsAbsoluteEpisodeNumbers = true
@@ -320,37 +336,40 @@ namespace Emby.Naming.Common
// *** End Kodi Standard Naming
new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})[^\\\/]*$")
                // [bar] Foo - 1 [baz]
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[-\s_]+(?<epnumber>\d+).*$")
{
IsNamed = true
},
new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>\d+)[xX](?<epnumber>\d+)[^\\\/]*$")
{
IsNamed = true
},
new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>\d{1,4})[x,X]?[eE](?<epnumber>\d{1,3})[^\\\/]*$")
new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>\d+)[x,X]?[eE](?<epnumber>\d+)[^\\\/]*$")
{
IsNamed = true
},
new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))[^\\\/]*$")
new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d+))[^\\\/]*$")
{
IsNamed = true
},
new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})[^\\\/]*$")
new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d+)[^\\\/]*$")
{
IsNamed = true
},
// "01.avi"
new EpisodeExpression(@".*[\\\/](?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\.\w+$")
new EpisodeExpression(@".*[\\\/](?<epnumber>\d+)(-(?<endingepnumber>\d+))*\.\w+$")
{
IsOptimistic = true,
IsNamed = true
},
// "1-12 episode title"
new EpisodeExpression(@"([0-9]+)-([0-9]+)")
{
},
new EpisodeExpression(@"([0-9]+)-([0-9]+)"),
// "01 - blah.avi", "01-blah.avi"
new EpisodeExpression(@".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\s?-\s?[^\\\/]*$")
@@ -427,7 +446,7 @@ namespace Emby.Naming.Common
Token = "_trailer",
MediaType = MediaType.Video
},
new ExtraRule
new ExtraRule
{
ExtraType = "trailer",
RuleType = ExtraRuleType.Suffix,
@@ -462,7 +481,7 @@ namespace Emby.Naming.Common
Token = "_sample",
MediaType = MediaType.Video
},
new ExtraRule
new ExtraRule
{
ExtraType = "sample",
RuleType = ExtraRuleType.Suffix,
@@ -476,7 +495,6 @@ namespace Emby.Naming.Common
Token = "theme",
MediaType = MediaType.Audio
},
new ExtraRule
{
ExtraType = "scene",
@@ -526,8 +544,8 @@ namespace Emby.Naming.Common
Token = "-short",
MediaType = MediaType.Video
}
};
Format3DRules = new[]
{
// Kodi rules:
@@ -648,11 +666,9 @@ namespace Emby.Naming.Common
@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$",
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$"
}.Select(i => new EpisodeExpression(i)
{
IsNamed = true
}).ToArray();
VideoFileExtensions = extensions

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
@@ -10,7 +10,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
</ItemGroup>
<PropertyGroup>
@@ -18,6 +18,19 @@
<PackageId>Jellyfin.Naming</PackageId>
<PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>

View File

@@ -5,6 +5,7 @@ namespace Emby.Naming.Extensions
{
public static class StringExtensions
{
// TODO: @bond remove this when moving to netstandard2.1
public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
{
var sb = new StringBuilder();

View File

@@ -1,30 +0,0 @@
using System;
using System.Text;
namespace Emby.Naming
{
internal static class StringExtensions
{
public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
{
var sb = new StringBuilder();
var previousIndex = 0;
var index = str.IndexOf(oldValue, comparison);
while (index != -1)
{
sb.Append(str.Substring(previousIndex, index - previousIndex));
sb.Append(newValue);
index += oldValue.Length;
previousIndex = index;
index = str.IndexOf(oldValue, index, comparison);
}
sb.Append(str.Substring(previousIndex));
return sb.ToString();
}
}
}

View File

@@ -7,16 +7,19 @@ namespace Emby.Naming.Subtitles
/// </summary>
/// <value>The path.</value>
public string Path { get; set; }
/// <summary>
/// Gets or sets the language.
/// </summary>
/// <value>The language.</value>
public string Language { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is default.
/// </summary>
/// <value><c>true</c> if this instance is default; otherwise, <c>false</c>.</value>
public bool IsDefault { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is forced.
/// </summary>

View File

@@ -7,31 +7,37 @@ namespace Emby.Naming.TV
/// </summary>
/// <value>The path.</value>
public string Path { get; set; }
/// <summary>
/// Gets or sets the container.
/// </summary>
/// <value>The container.</value>
public string Container { get; set; }
/// <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 the format3 d.
/// </summary>
/// <value>The format3 d.</value>
public string Format3D { get; set; }
/// <summary>
/// Gets or sets a value indicating whether [is3 d].
/// </summary>
/// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value>
public bool Is3D { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is stub.
/// </summary>
/// <value><c>true</c> if this instance is stub; otherwise, <c>false</c>.</value>
public bool IsStub { get; set; }
/// <summary>
/// Gets or sets the type of the stub.
/// </summary>
@@ -39,12 +45,17 @@ namespace Emby.Naming.TV
public string StubType { get; set; }
public int? SeasonNumber { get; set; }
public int? EpisodeNumber { get; set; }
public int? EndingEpsiodeNumber { get; set; }
public int? Year { get; set; }
public int? Month { get; set; }
public int? Day { get; set; }
public bool IsByDate { get; set; }
}
}

View File

@@ -15,12 +15,12 @@ namespace Emby.Naming.TV
_options = options;
}
public EpisodePathParserResult Parse(string path, bool IsDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true)
public EpisodePathParserResult Parse(string path, bool isDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true)
{
// Added to be able to use regex patterns which require a file extension.
// There were no failed tests without this block, but to be safe, we can keep it until
// the regex which require file extensions are modified so that they don't need them.
if (IsDirectory)
if (isDirectory)
{
path += ".mp4";
}
@@ -29,28 +29,20 @@ namespace Emby.Naming.TV
foreach (var expression in _options.EpisodeExpressions)
{
if (supportsAbsoluteNumbers.HasValue)
if (supportsAbsoluteNumbers.HasValue
&& expression.SupportsAbsoluteEpisodeNumbers != supportsAbsoluteNumbers.Value)
{
if (expression.SupportsAbsoluteEpisodeNumbers != supportsAbsoluteNumbers.Value)
{
continue;
}
continue;
}
if (isNamed.HasValue)
if (isNamed.HasValue && expression.IsNamed != isNamed.Value)
{
if (expression.IsNamed != isNamed.Value)
{
continue;
}
continue;
}
if (isOptimistic.HasValue)
if (isOptimistic.HasValue && expression.IsOptimistic != isOptimistic.Value)
{
if (expression.IsOptimistic != isOptimistic.Value)
{
continue;
}
continue;
}
var currentResult = Parse(path, expression);
@@ -97,7 +89,8 @@ namespace Emby.Naming.TV
DateTime date;
if (expression.DateTimeFormats.Length > 0)
{
if (DateTime.TryParseExact(match.Groups[0].Value,
if (DateTime.TryParseExact(
match.Groups[0].Value,
expression.DateTimeFormats,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
@@ -109,15 +102,12 @@ namespace Emby.Naming.TV
result.Success = true;
}
}
else
else if (DateTime.TryParse(match.Groups[0].Value, out date))
{
if (DateTime.TryParse(match.Groups[0].Value, out date))
{
result.Year = date.Year;
result.Month = date.Month;
result.Day = date.Day;
result.Success = true;
}
result.Year = date.Year;
result.Month = date.Month;
result.Day = date.Day;
result.Success = true;
}
// TODO: Only consider success if date successfully parsed?
@@ -142,7 +132,8 @@ namespace Emby.Naming.TV
// or a 'p' or 'i' as what you would get with a pixel resolution specification.
// It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode from E14 to E108
int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length;
if (nextIndex >= name.Length || "0123456789iIpP".IndexOf(name[nextIndex]) == -1)
if (nextIndex >= name.Length
|| "0123456789iIpP".IndexOf(name[nextIndex]) == -1)
{
if (int.TryParse(endingNumberGroup.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
{
@@ -160,6 +151,7 @@ namespace Emby.Naming.TV
{
result.SeasonNumber = num;
}
if (int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
{
result.EpisodeNumber = num;
@@ -171,8 +163,11 @@ namespace Emby.Naming.TV
// Invalidate match when the season is 200 through 1927 or above 2500
// because it is an error unless the TV show is intentionally using false season numbers.
// It avoids erroneous parsing of something like "Series Special (1920x1080).mkv" as being season 1920 episode 1080.
if (result.SeasonNumber >= 200 && result.SeasonNumber < 1928 || result.SeasonNumber > 2500)
if ((result.SeasonNumber >= 200 && result.SeasonNumber < 1928)
|| result.SeasonNumber > 2500)
{
result.Success = false;
}
result.IsByDate = expression.IsByDate;
}

View File

@@ -3,14 +3,21 @@ namespace Emby.Naming.TV
public class EpisodePathParserResult
{
public int? SeasonNumber { get; set; }
public int? EpisodeNumber { get; set; }
public int? EndingEpsiodeNumber { get; set; }
public string SeriesName { get; set; }
public bool Success { get; set; }
public bool IsByDate { get; set; }
public int? Year { get; set; }
public int? Month { get; set; }
public int? Day { get; set; }
}
}

View File

@@ -15,7 +15,13 @@ namespace Emby.Naming.TV
_options = options;
}
public EpisodeInfo Resolve(string path, bool IsDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true)
public EpisodeInfo Resolve(
string path,
bool isDirectory,
bool? isNamed = null,
bool? isOptimistic = null,
bool? supportsAbsoluteNumbers = null,
bool fillExtendedInfo = true)
{
if (string.IsNullOrEmpty(path))
{
@@ -26,7 +32,7 @@ namespace Emby.Naming.TV
string container = null;
string stubType = null;
if (!IsDirectory)
if (!isDirectory)
{
var extension = Path.GetExtension(path);
// Check supported extensions
@@ -52,7 +58,7 @@ namespace Emby.Naming.TV
var format3DResult = new Format3DParser(_options).Parse(flags);
var parsingResult = new EpisodePathParser(_options)
.Parse(path, IsDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo);
.Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo);
return new EpisodeInfo
{

View File

@@ -3,30 +3,24 @@ using System.Globalization;
using System.IO;
using System.Linq;
using Emby.Naming.Common;
using Emby.Naming.Extensions;
namespace Emby.Naming.TV
{
public class SeasonPathParser
{
private readonly NamingOptions _options;
public SeasonPathParser(NamingOptions options)
{
_options = options;
}
public SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
{
var result = new SeasonPathParserResult();
var seasonNumberInfo = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders);
result.SeasonNumber = seasonNumberInfo.Item1;
result.SeasonNumber = seasonNumberInfo.seasonNumber;
if (result.SeasonNumber.HasValue)
{
result.Success = true;
result.IsSeasonFolder = seasonNumberInfo.Item2;
result.IsSeasonFolder = seasonNumberInfo.isSeasonFolder;
}
return result;
@@ -35,7 +29,7 @@ namespace Emby.Naming.TV
/// <summary>
/// A season folder must contain one of these somewhere in the name
/// </summary>
private static readonly string[] SeasonFolderNames =
private static readonly string[] _seasonFolderNames =
{
"season",
"sæson",
@@ -54,19 +48,23 @@ namespace Emby.Naming.TV
/// <param name="supportSpecialAliases">if set to <c>true</c> [support special aliases].</param>
/// <param name="supportNumericSeasonFolders">if set to <c>true</c> [support numeric season folders].</param>
/// <returns>System.Nullable{System.Int32}.</returns>
private Tuple<int?, bool> GetSeasonNumberFromPath(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPath(
string path,
bool supportSpecialAliases,
bool supportNumericSeasonFolders)
{
var filename = Path.GetFileName(path);
var filename = Path.GetFileName(path) ?? string.Empty;
if (supportSpecialAliases)
{
if (string.Equals(filename, "specials", StringComparison.OrdinalIgnoreCase))
{
return new Tuple<int?, bool>(0, true);
return (0, true);
}
if (string.Equals(filename, "extras", StringComparison.OrdinalIgnoreCase))
{
return new Tuple<int?, bool>(0, true);
return (0, true);
}
}
@@ -74,7 +72,7 @@ namespace Emby.Naming.TV
{
if (int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
{
return new Tuple<int?, bool>(val, true);
return (val, true);
}
}
@@ -84,12 +82,12 @@ namespace Emby.Naming.TV
if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
{
return new Tuple<int?, bool>(val, true);
return (val, true);
}
}
// Look for one of the season folder names
foreach (var name in SeasonFolderNames)
foreach (var name in _seasonFolderNames)
{
var index = filename.IndexOf(name, StringComparison.OrdinalIgnoreCase);
@@ -107,10 +105,10 @@ namespace Emby.Naming.TV
var parts = filename.Split(new[] { '.', '_', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries);
var resultNumber = parts.Select(GetSeasonNumberFromPart).FirstOrDefault(i => i.HasValue);
return new Tuple<int?, bool>(resultNumber, true);
return (resultNumber, true);
}
private int? GetSeasonNumberFromPart(string part)
private static int? GetSeasonNumberFromPart(string part)
{
if (part.Length < 2 || !part.StartsWith("s", StringComparison.OrdinalIgnoreCase))
{
@@ -132,7 +130,7 @@ namespace Emby.Naming.TV
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.Nullable{System.Int32}.</returns>
private Tuple<int?, bool> GetSeasonNumberFromPathSubstring(string path)
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(string path)
{
var numericStart = -1;
var length = 0;
@@ -174,10 +172,10 @@ namespace Emby.Naming.TV
if (numericStart == -1)
{
return new Tuple<int?, bool>(null, isSeasonFolder);
return (null, isSeasonFolder);
}
return new Tuple<int?, bool>(int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture), isSeasonFolder);
return (int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture), isSeasonFolder);
}
}
}

View File

@@ -7,11 +7,13 @@ namespace Emby.Naming.TV
/// </summary>
/// <value>The season number.</value>
public int? SeasonNumber { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this <see cref="SeasonPathParserResult"/> is success.
/// </summary>
/// <value><c>true</c> if success; otherwise, <c>false</c>.</value>
public bool Success { get; set; }
public bool IsSeasonFolder { get; set; }
}
}

View File

@@ -27,8 +27,8 @@ namespace Emby.Naming.Video
{
var extension = Path.GetExtension(name) ?? string.Empty;
// Check supported extensions
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase) &&
!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)
&& !_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
// Dummy up a file extension because the expressions will fail without one
// This is tricky because we can't just check Path.GetExtension for empty
@@ -38,7 +38,6 @@ namespace Emby.Naming.Video
}
catch (ArgumentException)
{
}
var result = _options.CleanDateTimeRegexes.Select(i => Clean(name, i))
@@ -69,14 +68,15 @@ namespace Emby.Naming.Video
var match = expression.Match(name);
if (match.Success && match.Groups.Count == 4)
if (match.Success
&& match.Groups.Count == 4
&& match.Groups[1].Success
&& match.Groups[2].Success
&& int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
{
if (match.Groups[1].Success && match.Groups[2].Success && int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
{
name = match.Groups[1].Value;
result.Year = year;
result.HasChanged = true;
}
name = match.Groups[1].Value;
result.Year = year;
result.HasChanged = true;
}
result.Name = name;

View File

@@ -56,7 +56,6 @@ namespace Emby.Naming.Video
result.Rule = rule;
}
}
else if (rule.RuleType == ExtraRuleType.Suffix)
{
var filename = Path.GetFileNameWithoutExtension(path);
@@ -67,7 +66,6 @@ namespace Emby.Naming.Video
result.Rule = rule;
}
}
else if (rule.RuleType == ExtraRuleType.Regex)
{
var filename = Path.GetFileName(path);

View File

@@ -15,9 +15,9 @@ namespace Emby.Naming.Video
Files = new List<string>();
}
public bool ContainsFile(string file, bool IsDirectory)
public bool ContainsFile(string file, bool isDirectory)
{
if (IsDirectoryStack == IsDirectory)
if (IsDirectoryStack == isDirectory)
{
return Files.Contains(file, StringComparer.OrdinalIgnoreCase);
}

View File

@@ -15,10 +15,12 @@ namespace Emby.Naming.Video
public Format3DResult Parse(string path)
{
var delimeters = _options.VideoFlagDelimiters.ToList();
delimeters.Add(' ');
int oldLen = _options.VideoFlagDelimiters.Length;
var delimeters = new char[oldLen + 1];
_options.VideoFlagDelimiters.CopyTo(delimeters, 0);
delimeters[oldLen] = ' ';
return Parse(new FlagParser(_options).GetFlags(path, delimeters.ToArray()));
return Parse(new FlagParser(_options).GetFlags(path, delimeters));
}
internal Format3DResult Parse(string[] videoFlags)
@@ -66,8 +68,10 @@ namespace Emby.Naming.Video
format = flag;
result.Tokens.Add(rule.Token);
}
break;
}
foundPrefix = string.Equals(flag, rule.PreceedingToken, StringComparison.OrdinalIgnoreCase);
}

View File

@@ -4,25 +4,27 @@ namespace Emby.Naming.Video
{
public class Format3DResult
{
public Format3DResult()
{
Tokens = new List<string>();
}
/// <summary>
/// Gets or sets a value indicating whether [is3 d].
/// </summary>
/// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value>
public bool Is3D { get; set; }
/// <summary>
/// Gets or sets the format3 d.
/// </summary>
/// <value>The format3 d.</value>
public string Format3D { get; set; }
/// <summary>
/// Gets or sets the tokens.
/// </summary>
/// <value>The tokens.</value>
public List<string> Tokens { get; set; }
public Format3DResult()
{
Tokens = new List<string>();
}
}
}

View File

@@ -40,17 +40,24 @@ namespace Emby.Naming.Video
var result = new StackResult();
foreach (var directory in files.GroupBy(file => file.IsDirectory ? file.FullName : Path.GetDirectoryName(file.FullName)))
{
var stack = new FileStack();
stack.Name = Path.GetFileName(directory.Key);
stack.IsDirectoryStack = false;
var stack = new FileStack()
{
Name = Path.GetFileName(directory.Key),
IsDirectoryStack = false
};
foreach (var file in directory)
{
if (file.IsDirectory)
{
continue;
}
stack.Files.Add(file.FullName);
}
result.Stacks.Add(stack);
}
return result;
}
@@ -114,16 +121,16 @@ namespace Emby.Naming.Video
{
if (!string.Equals(volume1, volume2, StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase) &&
string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase))
if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase)
&& string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase))
{
if (stack.Files.Count == 0)
{
stack.Name = title1 + ignore1;
stack.IsDirectoryStack = file1.IsDirectory;
//stack.Name = title1 + ignore1 + extension1;
stack.Files.Add(file1.FullName);
}
stack.Files.Add(file2.FullName);
}
else

View File

@@ -9,24 +9,32 @@ namespace Emby.Naming.Video
{
public static StubResult ResolveFile(string path, NamingOptions options)
{
var result = new StubResult();
var extension = Path.GetExtension(path) ?? string.Empty;
if (options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
if (path == null)
{
result.IsStub = true;
return default(StubResult);
}
path = Path.GetFileNameWithoutExtension(path);
var extension = Path.GetExtension(path);
var token = (Path.GetExtension(path) ?? string.Empty).TrimStart('.');
if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
return default(StubResult);
}
foreach (var rule in options.StubTypes)
var result = new StubResult()
{
IsStub = true
};
path = Path.GetFileNameWithoutExtension(path);
var token = Path.GetExtension(path).TrimStart('.');
foreach (var rule in options.StubTypes)
{
if (string.Equals(rule.Token, token, StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(rule.Token, token, StringComparison.OrdinalIgnoreCase))
{
result.StubType = rule.StubType;
break;
}
result.StubType = rule.StubType;
break;
}
}

View File

@@ -7,6 +7,7 @@ namespace Emby.Naming.Video
/// </summary>
/// <value><c>true</c> if this instance is stub; otherwise, <c>false</c>.</value>
public bool IsStub { get; set; }
/// <summary>
/// Gets or sets the type of the stub.
/// </summary>

View File

@@ -7,6 +7,7 @@ namespace Emby.Naming.Video
/// </summary>
/// <value>The token.</value>
public string Token { get; set; }
/// <summary>
/// Gets or sets the type of the stub.
/// </summary>

View File

@@ -1,4 +1,3 @@
namespace Emby.Naming.Video
{
/// <summary>
@@ -11,56 +10,67 @@ namespace Emby.Naming.Video
/// </summary>
/// <value>The path.</value>
public string Path { get; set; }
/// <summary>
/// Gets or sets the container.
/// </summary>
/// <value>The container.</value>
public string Container { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the year.
/// </summary>
/// <value>The year.</value>
public int? Year { get; set; }
/// <summary>
/// Gets or sets the type of the extra, e.g. trailer, theme song, behing the scenes, etc.
/// </summary>
/// <value>The type of the extra.</value>
public string ExtraType { get; set; }
/// <summary>
/// Gets or sets the extra rule.
/// </summary>
/// <value>The extra rule.</value>
public ExtraRule ExtraRule { get; set; }
/// <summary>
/// Gets or sets the format3 d.
/// </summary>
/// <value>The format3 d.</value>
public string Format3D { get; set; }
/// <summary>
/// Gets or sets a value indicating whether [is3 d].
/// </summary>
/// <value><c>true</c> if [is3 d]; otherwise, <c>false</c>.</value>
public bool Is3D { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is stub.
/// </summary>
/// <value><c>true</c> if this instance is stub; otherwise, <c>false</c>.</value>
public bool IsStub { get; set; }
/// <summary>
/// Gets or sets the type of the stub.
/// </summary>
/// <value>The type of the stub.</value>
public string StubType { get; set; }
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
public bool IsDirectory { get; set; }
/// <summary>
/// Gets the file name without extension.
/// </summary>

View File

@@ -12,21 +12,25 @@ namespace Emby.Naming.Video
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the year.
/// </summary>
/// <value>The year.</value>
public int? Year { get; set; }
/// <summary>
/// Gets or sets the files.
/// </summary>
/// <value>The files.</value>
public List<VideoFileInfo> Files { get; set; }
/// <summary>
/// Gets or sets the extras.
/// </summary>
/// <value>The extras.</value>
public List<VideoFileInfo> Extras { get; set; }
/// <summary>
/// Gets or sets the alternate versions.
/// </summary>

View File

@@ -53,7 +53,7 @@ namespace Emby.Naming.Video
Name = stack.Name
};
info.Year = info.Files.First().Year;
info.Year = info.Files[0].Year;
var extraBaseNames = new List<string>
{
@@ -87,7 +87,7 @@ namespace Emby.Naming.Video
Name = media.Name
};
info.Year = info.Files.First().Year;
info.Year = info.Files[0].Year;
var extras = GetExtras(remainingFiles, new List<string> { media.FileNameWithoutExtension });
@@ -115,7 +115,7 @@ namespace Emby.Naming.Video
if (!string.IsNullOrEmpty(parentPath))
{
var folderName = Path.GetFileName(Path.GetDirectoryName(videoPath));
var folderName = Path.GetFileName(parentPath);
if (!string.IsNullOrEmpty(folderName))
{
var extras = GetExtras(remainingFiles, new List<string> { folderName });
@@ -163,9 +163,7 @@ namespace Emby.Naming.Video
Year = i.Year
}));
var orderedList = list.OrderBy(i => i.Name);
return orderedList;
return list.OrderBy(i => i.Name);
}
private IEnumerable<VideoInfo> GetVideosGroupedByVersion(List<VideoInfo> videos)
@@ -179,23 +177,21 @@ namespace Emby.Naming.Video
var folderName = Path.GetFileName(Path.GetDirectoryName(videos[0].Files[0].Path));
if (!string.IsNullOrEmpty(folderName) && folderName.Length > 1)
if (!string.IsNullOrEmpty(folderName)
&& folderName.Length > 1
&& videos.All(i => i.Files.Count == 1
&& IsEligibleForMultiVersion(folderName, i.Files[0].Path))
&& HaveSameYear(videos))
{
if (videos.All(i => i.Files.Count == 1 && IsEligibleForMultiVersion(folderName, i.Files[0].Path)))
{
if (HaveSameYear(videos))
{
var ordered = videos.OrderBy(i => i.Name).ToList();
var ordered = videos.OrderBy(i => i.Name).ToList();
list.Add(ordered[0]);
list.Add(ordered[0]);
list[0].AlternateVersions = ordered.Skip(1).Select(i => i.Files[0]).ToList();
list[0].Name = folderName;
list[0].Extras.AddRange(ordered.Skip(1).SelectMany(i => i.Extras));
list[0].AlternateVersions = ordered.Skip(1).Select(i => i.Files[0]).ToList();
list[0].Name = folderName;
list[0].Extras.AddRange(ordered.Skip(1).SelectMany(i => i.Extras));
return list;
}
}
return list;
}
return videos;
@@ -213,9 +209,9 @@ namespace Emby.Naming.Video
if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
{
testFilename = testFilename.Substring(folderName.Length).Trim();
return string.IsNullOrEmpty(testFilename) ||
testFilename.StartsWith("-") ||
string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)) ;
return string.IsNullOrEmpty(testFilename)
|| testFilename[0] == '-'
|| string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty));
}
return false;

View File

@@ -38,10 +38,11 @@ namespace Emby.Naming.Video
/// Resolves the specified path.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="IsDirectory">if set to <c>true</c> [is folder].</param>
/// <param name="isDirectory">if set to <c>true</c> [is folder].</param>
/// <param name="parseName">Whether or not the name should be parsed for info</param>
/// <returns>VideoFileInfo.</returns>
/// <exception cref="ArgumentNullException">path</exception>
public VideoFileInfo Resolve(string path, bool IsDirectory, bool parseName = true)
public VideoFileInfo Resolve(string path, bool isDirectory, bool parseName = true)
{
if (string.IsNullOrEmpty(path))
{
@@ -52,9 +53,10 @@ namespace Emby.Naming.Video
string container = null;
string stubType = null;
if (!IsDirectory)
if (!isDirectory)
{
var extension = Path.GetExtension(path);
// Check supported extensions
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
@@ -79,7 +81,7 @@ namespace Emby.Naming.Video
var extraResult = new ExtraResolver(_options).GetExtraInfo(path);
var name = IsDirectory
var name = isDirectory
? Path.GetFileName(path)
: Path.GetFileNameWithoutExtension(path);
@@ -108,7 +110,7 @@ namespace Emby.Naming.Video
Is3D = format3DResult.Is3D,
Format3D = format3DResult.Format3D,
ExtraType = extraResult.ExtraType,
IsDirectory = IsDirectory,
IsDirectory = isDirectory,
ExtraRule = extraResult.Rule
};
}

View File

@@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -88,7 +89,7 @@ namespace Emby.Notifications
return _userManager.Users.Where(i => i.Policy.IsAdministrator)
.Select(i => i.Id);
case SendToUserType.All:
return _userManager.Users.Select(i => i.Id);
return _userManager.UsersIds;
case SendToUserType.Custom:
return request.UserIds;
default:
@@ -101,7 +102,7 @@ namespace Emby.Notifications
var config = GetConfiguration();
return _userManager.Users
.Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Policy))
.Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i.Policy))
.Select(i => i.Id);
}
@@ -197,7 +198,7 @@ namespace Emby.Notifications
return _services.Select(i => new NameIdPair
{
Name = i.Name,
Id = i.Name.GetMD5().ToString("N")
Id = i.Name.GetMD5().ToString("N", CultureInfo.InvariantCulture)
}).OrderBy(i => i.Name);
}

View File

@@ -209,47 +209,48 @@ namespace Emby.Notifications
public static string GetItemName(BaseItem item)
{
var name = item.Name;
var episode = item as Episode;
if (episode != null)
if (item is Episode episode)
{
if (episode.IndexNumber.HasValue)
{
name = string.Format("Ep{0} - {1}", episode.IndexNumber.Value.ToString(CultureInfo.InvariantCulture), name);
name = string.Format(
CultureInfo.InvariantCulture,
"Ep{0} - {1}",
episode.IndexNumber.Value,
name);
}
if (episode.ParentIndexNumber.HasValue)
{
name = string.Format("S{0}, {1}", episode.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture), name);
name = string.Format(
CultureInfo.InvariantCulture,
"S{0}, {1}",
episode.ParentIndexNumber.Value,
name);
}
}
var hasSeries = item as IHasSeries;
if (hasSeries != null)
if (item is IHasSeries hasSeries)
{
name = hasSeries.SeriesName + " - " + name;
}
var hasAlbumArtist = item as IHasAlbumArtist;
if (hasAlbumArtist != null)
if (item is IHasAlbumArtist hasAlbumArtist)
{
var artists = hasAlbumArtist.AlbumArtists;
if (artists.Length > 0)
if (artists.Count > 0)
{
name = artists[0] + " - " + name;
}
}
else
else if (item is IHasArtist hasArtist)
{
var hasArtist = item as IHasArtist;
if (hasArtist != null)
{
var artists = hasArtist.Artists;
var artists = hasArtist.Artists;
if (artists.Length > 0)
{
name = artists[0] + " - " + name;
}
if (artists.Count > 0)
{
name = artists[0] + " - " + name;
}
}

View File

@@ -10,12 +10,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="TagLibSharp" Version="2.2.0-beta" />
<PackageReference Include="TagLibSharp" Version="2.2.0" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>

View File

@@ -20,7 +20,10 @@ namespace Emby.Photos
public class PhotoProvider : ICustomMetadataProvider<Photo>, IForcedProvider, IHasItemChangeMonitor
{
private readonly ILogger _logger;
private IImageProcessor _imageProcessor;
private readonly IImageProcessor _imageProcessor;
// These are causing taglib to hang
private string[] _includextensions = new string[] { ".jpg", ".jpeg", ".png", ".tiff", ".cr2" };
public PhotoProvider(ILogger logger, IImageProcessor imageProcessor)
{
@@ -28,75 +31,55 @@ namespace Emby.Photos
_imageProcessor = imageProcessor;
}
public string Name => "Embedded Information";
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
{
if (item.IsFileProtocol)
{
var file = directoryService.GetFile(item.Path);
if (file != null && file.LastWriteTimeUtc != item.DateModified)
{
return true;
}
return (file != null && file.LastWriteTimeUtc != item.DateModified);
}
return false;
}
// These are causing taglib to hang
private string[] _includextensions = new string[] { ".jpg", ".jpeg", ".png", ".tiff", ".cr2" };
public Task<ItemUpdateType> FetchAsync(Photo item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
item.SetImagePath(ImageType.Primary, item.Path);
// Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs
if (_includextensions.Contains(Path.GetExtension(item.Path) ?? string.Empty, StringComparer.OrdinalIgnoreCase))
if (_includextensions.Contains(Path.GetExtension(item.Path), StringComparer.OrdinalIgnoreCase))
{
try
{
using (var file = TagLib.File.Create(item.Path))
{
var image = file as TagLib.Image.File;
var tag = file.GetTag(TagTypes.TiffIFD) as IFDTag;
if (tag != null)
if (file.GetTag(TagTypes.TiffIFD) is IFDTag tag)
{
var structure = tag.Structure;
if (structure != null)
if (structure != null
&& structure.GetEntry(0, (ushort)IFDEntryTag.ExifIFD) is SubIFDEntry exif)
{
var exif = structure.GetEntry(0, (ushort)IFDEntryTag.ExifIFD) as SubIFDEntry;
if (exif != null)
var exifStructure = exif.Structure;
if (exifStructure != null)
{
var exifStructure = exif.Structure;
if (exifStructure != null)
var entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ApertureValue) as RationalIFDEntry;
if (entry != null)
{
var entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ApertureValue) as RationalIFDEntry;
item.Aperture = (double)entry.Value.Numerator / entry.Value.Denominator;
}
if (entry != null)
{
double val = entry.Value.Numerator;
val /= entry.Value.Denominator;
item.Aperture = val;
}
entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ShutterSpeedValue) as RationalIFDEntry;
if (entry != null)
{
double val = entry.Value.Numerator;
val /= entry.Value.Denominator;
item.ShutterSpeed = val;
}
entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ShutterSpeedValue) as RationalIFDEntry;
if (entry != null)
{
item.ShutterSpeed = (double)entry.Value.Numerator / entry.Value.Denominator;
}
}
}
}
if (image != null)
if (file is TagLib.Image.File image)
{
item.CameraMake = image.ImageTag.Make;
item.CameraModel = image.ImageTag.Model;
@@ -116,12 +99,10 @@ namespace Emby.Photos
item.Overview = image.ImageTag.Comment;
if (!string.IsNullOrWhiteSpace(image.ImageTag.Title))
if (!string.IsNullOrWhiteSpace(image.ImageTag.Title)
&& !item.LockedFields.Contains(MetadataFields.Name))
{
if (!item.LockedFields.Contains(MetadataFields.Name))
{
item.Name = image.ImageTag.Title;
}
item.Name = image.ImageTag.Title;
}
var dateTaken = image.ImageTag.DateTime;
@@ -140,12 +121,9 @@ namespace Emby.Photos
{
item.Orientation = null;
}
else
else if (Enum.TryParse(image.ImageTag.Orientation.ToString(), true, out ImageOrientation orientation))
{
if (Enum.TryParse(image.ImageTag.Orientation.ToString(), true, out ImageOrientation orientation))
{
item.Orientation = orientation;
}
item.Orientation = orientation;
}
item.ExposureTime = image.ImageTag.ExposureTime;
@@ -192,10 +170,8 @@ namespace Emby.Photos
}
}
const ItemUpdateType result = ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataImport;
return Task.FromResult(result);
const ItemUpdateType Result = ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataImport;
return Task.FromResult(Result);
}
public string Name => "Embedded Information";
}
}

View File

@@ -1,14 +1,13 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -29,31 +28,39 @@ namespace Emby.Server.Implementations.Activity
{
public class ActivityLogEntryPoint : IServerEntryPoint
{
private readonly ILogger _logger;
private readonly IInstallationManager _installationManager;
private readonly ISessionManager _sessionManager;
private readonly ITaskManager _taskManager;
private readonly IActivityManager _activityManager;
private readonly ILocalizationManager _localization;
private readonly ILibraryManager _libraryManager;
private readonly ISubtitleManager _subManager;
private readonly IUserManager _userManager;
private readonly IServerConfigurationManager _config;
private readonly IServerApplicationHost _appHost;
private readonly IDeviceManager _deviceManager;
public ActivityLogEntryPoint(ISessionManager sessionManager, IDeviceManager deviceManager, ITaskManager taskManager, IActivityManager activityManager, ILocalizationManager localization, IInstallationManager installationManager, ILibraryManager libraryManager, ISubtitleManager subManager, IUserManager userManager, IServerConfigurationManager config, IServerApplicationHost appHost)
public ActivityLogEntryPoint(
ILogger<ActivityLogEntryPoint> logger,
ISessionManager sessionManager,
IDeviceManager deviceManager,
ITaskManager taskManager,
IActivityManager activityManager,
ILocalizationManager localization,
IInstallationManager installationManager,
ISubtitleManager subManager,
IUserManager userManager,
IServerApplicationHost appHost)
{
_logger = logger;
_sessionManager = sessionManager;
_deviceManager = deviceManager;
_taskManager = taskManager;
_activityManager = activityManager;
_localization = localization;
_installationManager = installationManager;
_libraryManager = libraryManager;
_subManager = subManager;
_userManager = userManager;
_config = config;
_appHost = appHost;
_deviceManager = deviceManager;
}
public Task RunAsync()
@@ -69,7 +76,6 @@ namespace Emby.Server.Implementations.Activity
_sessionManager.AuthenticationFailed += OnAuthenticationFailed;
_sessionManager.AuthenticationSucceeded += OnAuthenticationSucceeded;
_sessionManager.SessionEnded += OnSessionEnded;
_sessionManager.PlaybackStart += OnPlaybackStart;
_sessionManager.PlaybackStopped += OnPlaybackStopped;
@@ -83,8 +89,6 @@ namespace Emby.Server.Implementations.Activity
_deviceManager.CameraImageUploaded += OnCameraImageUploaded;
_appHost.ApplicationUpdated += OnApplicationUpdated;
return Task.CompletedTask;
}
@@ -92,7 +96,10 @@ namespace Emby.Server.Implementations.Activity
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("CameraImageUploadedFrom"), e.Argument.Device.Name),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("CameraImageUploadedFrom"),
e.Argument.Device.Name),
Type = NotificationType.CameraImageUploaded.ToString()
});
}
@@ -101,7 +108,10 @@ namespace Emby.Server.Implementations.Activity
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserLockedOutWithName"), e.Argument.Name),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserLockedOutWithName"),
e.Argument.Name),
Type = NotificationType.UserLockedOut.ToString(),
UserId = e.Argument.Id
});
@@ -111,9 +121,13 @@ namespace Emby.Server.Implementations.Activity
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), e.Provider, Notifications.Notifications.GetItemName(e.Item)),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
e.Provider,
Notifications.Notifications.GetItemName(e.Item)),
Type = "SubtitleDownloadFailure",
ItemId = e.Item.Id.ToString("N"),
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
ShortOverview = e.Exception.Message
});
}
@@ -124,7 +138,7 @@ namespace Emby.Server.Implementations.Activity
if (item == null)
{
//_logger.LogWarning("PlaybackStopped reported with null media info.");
_logger.LogWarning("PlaybackStopped reported with null media info.");
return;
}
@@ -155,7 +169,7 @@ namespace Emby.Server.Implementations.Activity
if (item == null)
{
//_logger.LogWarning("PlaybackStart reported with null media info.");
_logger.LogWarning("PlaybackStart reported with null media info.");
return;
}
@@ -174,7 +188,12 @@ namespace Emby.Server.Implementations.Activity
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserStartedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
user.Name,
GetItemName(item),
e.DeviceName),
Type = GetPlaybackNotificationType(item.MediaType),
UserId = user.Id
});
@@ -189,7 +208,7 @@ namespace Emby.Server.Implementations.Activity
name = item.SeriesName + " - " + name;
}
if (item.Artists != null && item.Artists.Length > 0)
if (item.Artists != null && item.Artists.Count > 0)
{
name = item.Artists[0] + " - " + name;
}
@@ -203,6 +222,7 @@ namespace Emby.Server.Implementations.Activity
{
return NotificationType.AudioPlayback.ToString();
}
if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
return NotificationType.VideoPlayback.ToString();
@@ -217,6 +237,7 @@ namespace Emby.Server.Implementations.Activity
{
return NotificationType.AudioPlaybackStopped.ToString();
}
if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
return NotificationType.VideoPlaybackStopped.ToString();
@@ -232,21 +253,31 @@ namespace Emby.Server.Implementations.Activity
if (string.IsNullOrEmpty(session.UserName))
{
name = string.Format(_localization.GetLocalizedString("DeviceOfflineWithName"), session.DeviceName);
name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("DeviceOfflineWithName"),
session.DeviceName);
// Causing too much spam for now
return;
}
else
{
name = string.Format(_localization.GetLocalizedString("UserOfflineFromDevice"), session.UserName, session.DeviceName);
name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserOfflineFromDevice"),
session.UserName,
session.DeviceName);
}
CreateLogEntry(new ActivityLogEntry
{
Name = name,
Type = "SessionEnded",
ShortOverview = string.Format(_localization.GetLocalizedString("LabelIpAddressValue"), session.RemoteEndPoint),
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"),
session.RemoteEndPoint),
UserId = session.UserId
});
}
@@ -257,9 +288,15 @@ namespace Emby.Server.Implementations.Activity
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("AuthenticationSucceededWithUserName"), user.Name),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("AuthenticationSucceededWithUserName"),
user.Name),
Type = "AuthenticationSucceeded",
ShortOverview = string.Format(_localization.GetLocalizedString("LabelIpAddressValue"), e.Argument.SessionInfo.RemoteEndPoint),
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"),
e.Argument.SessionInfo.RemoteEndPoint),
UserId = user.Id
});
}
@@ -268,28 +305,27 @@ namespace Emby.Server.Implementations.Activity
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("FailedLoginAttemptWithUserName"), e.Argument.Username),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("FailedLoginAttemptWithUserName"),
e.Argument.Username),
Type = "AuthenticationFailed",
ShortOverview = string.Format(_localization.GetLocalizedString("LabelIpAddressValue"), e.Argument.RemoteEndPoint),
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"),
e.Argument.RemoteEndPoint),
Severity = LogLevel.Error
});
}
private void OnApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("MessageApplicationUpdatedTo"), e.Argument.versionStr),
Type = NotificationType.ApplicationUpdateInstalled.ToString(),
Overview = e.Argument.description
});
}
private void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserPolicyUpdatedWithName"), e.Argument.Name),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserPolicyUpdatedWithName"),
e.Argument.Name),
Type = "UserPolicyUpdated",
UserId = e.Argument.Id
});
@@ -299,7 +335,10 @@ namespace Emby.Server.Implementations.Activity
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserDeletedWithName"), e.Argument.Name),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserDeletedWithName"),
e.Argument.Name),
Type = "UserDeleted"
});
}
@@ -308,7 +347,10 @@ namespace Emby.Server.Implementations.Activity
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserPasswordChangedWithName"), e.Argument.Name),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserPasswordChangedWithName"),
e.Argument.Name),
Type = "UserPasswordChanged",
UserId = e.Argument.Id
});
@@ -318,7 +360,10 @@ namespace Emby.Server.Implementations.Activity
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserCreatedWithName"), e.Argument.Name),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserCreatedWithName"),
e.Argument.Name),
Type = "UserCreated",
UserId = e.Argument.Id
});
@@ -331,32 +376,48 @@ namespace Emby.Server.Implementations.Activity
if (string.IsNullOrEmpty(session.UserName))
{
name = string.Format(_localization.GetLocalizedString("DeviceOnlineWithName"), session.DeviceName);
name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("DeviceOnlineWithName"),
session.DeviceName);
// Causing too much spam for now
return;
}
else
{
name = string.Format(_localization.GetLocalizedString("UserOnlineFromDevice"), session.UserName, session.DeviceName);
name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserOnlineFromDevice"),
session.UserName,
session.DeviceName);
}
CreateLogEntry(new ActivityLogEntry
{
Name = name,
Type = "SessionStarted",
ShortOverview = string.Format(_localization.GetLocalizedString("LabelIpAddressValue"), session.RemoteEndPoint),
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"),
session.RemoteEndPoint),
UserId = session.UserId
});
}
private void OnPluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e)
private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, PackageVersionInfo)> e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("PluginUpdatedWithName"), e.Argument.Item1.Name),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginUpdatedWithName"),
e.Argument.Item1.Name),
Type = NotificationType.PluginUpdateInstalled.ToString(),
ShortOverview = string.Format(_localization.GetLocalizedString("VersionNumber"), e.Argument.Item2.versionStr),
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"),
e.Argument.Item2.versionStr),
Overview = e.Argument.Item2.description
});
}
@@ -365,7 +426,10 @@ namespace Emby.Server.Implementations.Activity
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("PluginUninstalledWithName"), e.Argument.Name),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginUninstalledWithName"),
e.Argument.Name),
Type = NotificationType.PluginUninstalled.ToString()
});
}
@@ -374,9 +438,15 @@ namespace Emby.Server.Implementations.Activity
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("PluginInstalledWithName"), e.Argument.name),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginInstalledWithName"),
e.Argument.name),
Type = NotificationType.PluginInstalled.ToString(),
ShortOverview = string.Format(_localization.GetLocalizedString("VersionNumber"), e.Argument.versionStr)
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"),
e.Argument.versionStr)
});
}
@@ -386,9 +456,15 @@ namespace Emby.Server.Implementations.Activity
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("NameInstallFailed"), installationInfo.Name),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("NameInstallFailed"),
installationInfo.Name),
Type = NotificationType.InstallationFailed.ToString(),
ShortOverview = string.Format(_localization.GetLocalizedString("VersionNumber"), installationInfo.Version),
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"),
installationInfo.Version),
Overview = e.Exception.Message
});
}
@@ -405,7 +481,10 @@ namespace Emby.Server.Implementations.Activity
}
var time = result.EndTimeUtc - result.StartTimeUtc;
var runningTime = string.Format(_localization.GetLocalizedString("LabelRunningTimeValue"), ToUserFriendlyString(time));
var runningTime = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelRunningTimeValue"),
ToUserFriendlyString(time));
if (result.Status == TaskCompletionStatus.Failed)
{
@@ -415,6 +494,7 @@ namespace Emby.Server.Implementations.Activity
{
vals.Add(e.Result.ErrorMessage);
}
if (!string.IsNullOrEmpty(e.Result.LongErrorMessage))
{
vals.Add(e.Result.LongErrorMessage);
@@ -422,9 +502,12 @@ namespace Emby.Server.Implementations.Activity
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("ScheduledTaskFailedWithName"),
task.Name),
Type = NotificationType.TaskFailed.ToString(),
Overview = string.Join(Environment.NewLine, vals.ToArray()),
Overview = string.Join(Environment.NewLine, vals),
ShortOverview = runningTime,
Severity = LogLevel.Error
});
@@ -460,8 +543,6 @@ namespace Emby.Server.Implementations.Activity
_userManager.UserLockedOut -= OnUserLockedOut;
_deviceManager.CameraImageUploaded -= OnCameraImageUploaded;
_appHost.ApplicationUpdated -= OnApplicationUpdated;
}
/// <summary>
@@ -503,6 +584,7 @@ namespace Emby.Server.Implementations.Activity
{
values.Add(CreateValueString(span.Hours, "hour"));
}
// Number of minutes
if (span.Minutes >= 1)
{
@@ -526,6 +608,7 @@ namespace Emby.Server.Implementations.Activity
builder.Append(values[i]);
}
// Return result
return builder.ToString();
}
@@ -537,8 +620,11 @@ namespace Emby.Server.Implementations.Activity
/// <param name="description">The name of this item (singular form)</param>
private static string CreateValueString(int value, string description)
{
return string.Format("{0:#,##0} {1}",
value, value == 1 ? description : string.Format("{0}s", description));
return string.Format(
CultureInfo.InvariantCulture,
"{0:#,##0} {1}",
value,
value == 1 ? description : string.Format(CultureInfo.InvariantCulture, "{0}s", description));
}
}
}

View File

@@ -15,14 +15,14 @@ namespace Emby.Server.Implementations.Activity
{
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
protected IFileSystem FileSystem { get; private set; }
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
private readonly IFileSystem _fileSystem;
public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem)
: base(loggerFactory.CreateLogger(nameof(ActivityRepository)))
{
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
FileSystem = fileSystem;
_fileSystem = fileSystem;
}
public void Initialize()
@@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.Activity
{
Logger.LogError(ex, "Error loading database file. Will reset and retry.");
FileSystem.DeleteFile(DbFilePath);
_fileSystem.DeleteFile(DbFilePath);
InitializeInternal();
}
@@ -43,10 +43,8 @@ namespace Emby.Server.Implementations.Activity
private void InitializeInternal()
{
using (var connection = CreateConnection())
using (var connection = GetConnection())
{
RunDefaultInitialization(connection);
connection.RunQueries(new[]
{
"create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)",
@@ -85,8 +83,7 @@ namespace Emby.Server.Implementations.Activity
throw new ArgumentNullException(nameof(entry));
}
using (WriteLock.Write())
using (var connection = CreateConnection())
using (var connection = GetConnection())
{
connection.RunInTransaction(db =>
{
@@ -105,7 +102,7 @@ namespace Emby.Server.Implementations.Activity
}
else
{
statement.TryBind("@UserId", entry.UserId.ToString("N"));
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
}
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
@@ -124,8 +121,7 @@ namespace Emby.Server.Implementations.Activity
throw new ArgumentNullException(nameof(entry));
}
using (WriteLock.Write())
using (var connection = CreateConnection())
using (var connection = GetConnection())
{
connection.RunInTransaction(db =>
{
@@ -145,7 +141,7 @@ namespace Emby.Server.Implementations.Activity
}
else
{
statement.TryBind("@UserId", entry.UserId.ToString("N"));
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
}
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
@@ -159,95 +155,100 @@ namespace Emby.Server.Implementations.Activity
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
{
using (WriteLock.Read())
using (var connection = CreateConnection(true))
var commandText = BaseActivitySelectText;
var whereClauses = new List<string>();
if (minDate.HasValue)
{
var commandText = BaseActivitySelectText;
var whereClauses = new List<string>();
if (minDate.HasValue)
whereClauses.Add("DateCreated>=@DateCreated");
}
if (hasUserId.HasValue)
{
if (hasUserId.Value)
{
whereClauses.Add("DateCreated>=@DateCreated");
whereClauses.Add("UserId not null");
}
if (hasUserId.HasValue)
else
{
if (hasUserId.Value)
{
whereClauses.Add("UserId not null");
}
else
{
whereClauses.Add("UserId is null");
}
whereClauses.Add("UserId is null");
}
}
var whereTextWithoutPaging = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
var whereTextWithoutPaging = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
if (startIndex.HasValue && startIndex.Value > 0)
{
var pagingWhereText = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})",
pagingWhereText,
startIndex.Value.ToString(_usCulture)));
}
var whereText = whereClauses.Count == 0 ?
if (startIndex.HasValue && startIndex.Value > 0)
{
var pagingWhereText = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
commandText += whereText;
commandText += " ORDER BY DateCreated DESC";
if (limit.HasValue)
{
commandText += " LIMIT " + limit.Value.ToString(_usCulture);
}
var statementTexts = new List<string>();
statementTexts.Add(commandText);
statementTexts.Add("select count (Id) from ActivityLog" + whereTextWithoutPaging);
return connection.RunInTransaction(db =>
{
var list = new List<ActivityLogEntry>();
var result = new QueryResult<ActivityLogEntry>();
var statements = PrepareAllSafe(db, statementTexts).ToList();
using (var statement = statements[0])
{
if (minDate.HasValue)
{
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
foreach (var row in statement.ExecuteQuery())
{
list.Add(GetEntry(row));
}
}
using (var statement = statements[1])
{
if (minDate.HasValue)
{
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
}
result.Items = list.ToArray();
return result;
}, ReadTransactionMode);
whereClauses.Add(
string.Format(
CultureInfo.InvariantCulture,
"Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})",
pagingWhereText,
startIndex.Value));
}
var whereText = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
commandText += whereText;
commandText += " ORDER BY DateCreated DESC";
if (limit.HasValue)
{
commandText += " LIMIT " + limit.Value.ToString(_usCulture);
}
var statementTexts = new[]
{
commandText,
"select count (Id) from ActivityLog" + whereTextWithoutPaging
};
var list = new List<ActivityLogEntry>();
var result = new QueryResult<ActivityLogEntry>();
using (var connection = GetConnection(true))
{
connection.RunInTransaction(
db =>
{
var statements = PrepareAll(db, statementTexts).ToList();
using (var statement = statements[0])
{
if (minDate.HasValue)
{
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
foreach (var row in statement.ExecuteQuery())
{
list.Add(GetEntry(row));
}
}
using (var statement = statements[1])
{
if (minDate.HasValue)
{
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
}
},
ReadTransactionMode);
}
result.Items = list;
return result;
}
private static ActivityLogEntry GetEntry(IReadOnlyList<IResultSetValue> reader)

View File

@@ -10,6 +10,8 @@ namespace Emby.Server.Implementations.AppBase
/// </summary>
public abstract class BaseApplicationPaths : IApplicationPaths
{
private string _dataPath;
/// <summary>
/// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
/// </summary>
@@ -30,27 +32,27 @@ namespace Emby.Server.Implementations.AppBase
}
/// <summary>
/// Gets the path to the program data folder
/// Gets the path to the program data folder.
/// </summary>
/// <value>The program data path.</value>
public string ProgramDataPath { get; private set; }
public string ProgramDataPath { get; }
/// <summary>
/// Gets the path to the web UI resources folder
/// Gets the path to the web UI resources folder.
/// </summary>
/// <value>The web UI resources path.</value>
public string WebPath { get; set; }
public string WebPath { get; }
/// <summary>
/// Gets the path to the system folder
/// Gets the path to the system folder.
/// </summary>
/// <value>The path to the system folder.</value>
public string ProgramSystemPath { get; } = AppContext.BaseDirectory;
/// <summary>
/// Gets the folder path to the data directory
/// Gets the folder path to the data directory.
/// </summary>
/// <value>The data directory.</value>
private string _dataPath;
public string DataPath
{
get => _dataPath;
@@ -58,8 +60,9 @@ namespace Emby.Server.Implementations.AppBase
}
/// <summary>
/// Gets the magic strings used for virtual path manipulation.
/// Gets the magic string used for virtual path manipulation.
/// </summary>
/// <value>The magic string used for virtual path manipulation.</value>
public string VirtualDataPath { get; } = "%AppDataPath%";
/// <summary>
@@ -69,43 +72,43 @@ namespace Emby.Server.Implementations.AppBase
public string ImageCachePath => Path.Combine(CachePath, "images");
/// <summary>
/// Gets the path to the plugin directory
/// Gets the path to the plugin directory.
/// </summary>
/// <value>The plugins path.</value>
public string PluginsPath => Path.Combine(ProgramDataPath, "plugins");
/// <summary>
/// Gets the path to the plugin configurations directory
/// Gets the path to the plugin configurations directory.
/// </summary>
/// <value>The plugin configurations path.</value>
public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations");
/// <summary>
/// Gets the path to the log directory
/// Gets the path to the log directory.
/// </summary>
/// <value>The log directory path.</value>
public string LogDirectoryPath { get; private set; }
public string LogDirectoryPath { get; }
/// <summary>
/// Gets the path to the application configuration root directory
/// Gets the path to the application configuration root directory.
/// </summary>
/// <value>The configuration directory path.</value>
public string ConfigurationDirectoryPath { get; private set; }
public string ConfigurationDirectoryPath { get; }
/// <summary>
/// Gets the path to the system configuration file
/// Gets the path to the system configuration file.
/// </summary>
/// <value>The system configuration file path.</value>
public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml");
/// <summary>
/// Gets the folder path to the cache directory
/// Gets or sets the folder path to the cache directory.
/// </summary>
/// <value>The cache directory.</value>
public string CachePath { get; set; }
/// <summary>
/// Gets the folder path to the temp directory within the cache folder
/// Gets the folder path to the temp directory within the cache folder.
/// </summary>
/// <value>The temp directory.</value>
public string TempDirectory => Path.Combine(CachePath, "temp");

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
@@ -19,11 +20,44 @@ namespace Emby.Server.Implementations.AppBase
/// </summary>
public abstract class BaseConfigurationManager : IConfigurationManager
{
private readonly IFileSystem _fileSystem;
private readonly ConcurrentDictionary<string, object> _configurations = new ConcurrentDictionary<string, object>();
private ConfigurationStore[] _configurationStores = Array.Empty<ConfigurationStore>();
private IConfigurationFactory[] _configurationFactories = Array.Empty<IConfigurationFactory>();
/// <summary>
/// Gets the type of the configuration.
/// The _configuration loaded.
/// </summary>
/// <value>The type of the configuration.</value>
protected abstract Type ConfigurationType { get; }
private bool _configurationLoaded;
/// <summary>
/// The _configuration sync lock.
/// </summary>
private object _configurationSyncLock = new object();
/// <summary>
/// The _configuration.
/// </summary>
private BaseApplicationConfiguration _configuration;
/// <summary>
/// Initializes a new instance of the <see cref="BaseConfigurationManager" /> class.
/// </summary>
/// <param name="applicationPaths">The application paths.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="xmlSerializer">The XML serializer.</param>
/// <param name="fileSystem">The file system</param>
protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
{
CommonApplicationPaths = applicationPaths;
XmlSerializer = xmlSerializer;
_fileSystem = fileSystem;
Logger = loggerFactory.CreateLogger(GetType().Name);
UpdateCachePath();
}
/// <summary>
/// Occurs when [configuration updated].
@@ -40,6 +74,12 @@ namespace Emby.Server.Implementations.AppBase
/// </summary>
public event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdated;
/// <summary>
/// Gets the type of the configuration.
/// </summary>
/// <value>The type of the configuration.</value>
protected abstract Type ConfigurationType { get; }
/// <summary>
/// Gets the logger.
/// </summary>
@@ -56,20 +96,7 @@ namespace Emby.Server.Implementations.AppBase
/// </summary>
/// <value>The application paths.</value>
public IApplicationPaths CommonApplicationPaths { get; private set; }
public readonly IFileSystem FileSystem;
/// <summary>
/// The _configuration loaded
/// </summary>
private bool _configurationLoaded;
/// <summary>
/// The _configuration sync lock
/// </summary>
private object _configurationSyncLock = new object();
/// <summary>
/// The _configuration
/// </summary>
private BaseApplicationConfiguration _configuration;
/// <summary>
/// Gets the system configuration
/// </summary>
@@ -90,26 +117,6 @@ namespace Emby.Server.Implementations.AppBase
}
}
private ConfigurationStore[] _configurationStores = { };
private IConfigurationFactory[] _configurationFactories = { };
/// <summary>
/// Initializes a new instance of the <see cref="BaseConfigurationManager" /> class.
/// </summary>
/// <param name="applicationPaths">The application paths.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="xmlSerializer">The XML serializer.</param>
/// <param name="fileSystem">The file system</param>
protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
{
CommonApplicationPaths = applicationPaths;
XmlSerializer = xmlSerializer;
FileSystem = fileSystem;
Logger = loggerFactory.CreateLogger(GetType().Name);
UpdateCachePath();
}
public virtual void AddParts(IEnumerable<IConfigurationFactory> factories)
{
_configurationFactories = factories.ToArray();
@@ -171,6 +178,7 @@ namespace Emby.Server.Implementations.AppBase
private void UpdateCachePath()
{
string cachePath;
// If the configuration file has no entry (i.e. not set in UI)
if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath))
{
@@ -207,12 +215,16 @@ namespace Emby.Server.Implementations.AppBase
var newPath = newConfig.CachePath;
if (!string.IsNullOrWhiteSpace(newPath)
&& !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath))
&& !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath, StringComparison.Ordinal))
{
// Validate
if (!Directory.Exists(newPath))
{
throw new FileNotFoundException(string.Format("{0} does not exist.", newPath));
throw new FileNotFoundException(
string.Format(
CultureInfo.InvariantCulture,
"{0} does not exist.",
newPath));
}
EnsureWriteAccess(newPath);
@@ -223,11 +235,9 @@ namespace Emby.Server.Implementations.AppBase
{
var file = Path.Combine(path, Guid.NewGuid().ToString());
File.WriteAllText(file, string.Empty);
FileSystem.DeleteFile(file);
_fileSystem.DeleteFile(file);
}
private readonly ConcurrentDictionary<string, object> _configurations = new ConcurrentDictionary<string, object>();
private string GetConfigurationFile(string key)
{
return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLowerInvariant() + ".xml");

View File

@@ -6,6 +6,8 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
@@ -106,9 +108,9 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using ServiceStack;
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
@@ -119,6 +121,10 @@ namespace Emby.Server.Implementations
/// </summary>
public abstract class ApplicationHost : IServerApplicationHost, IDisposable
{
private SqliteUserRepository _userRepository;
private SqliteDisplayPreferencesRepository _displayPreferencesRepository;
/// <summary>
/// Gets a value indicating whether this instance can self restart.
/// </summary>
@@ -154,11 +160,6 @@ namespace Emby.Server.Implementations
/// </summary>
public event EventHandler HasPendingRestartChanged;
/// <summary>
/// Occurs when [application updated].
/// </summary>
public event EventHandler<GenericEventArgs<PackageVersionInfo>> ApplicationUpdated;
/// <summary>
/// Gets a value indicating whether this instance has changes that require the entire application to restart.
/// </summary>
@@ -201,10 +202,10 @@ namespace Emby.Server.Implementations
/// Gets or sets all concrete types.
/// </summary>
/// <value>All concrete types.</value>
public Type[] AllConcreteTypes { get; protected set; }
private Type[] _allConcreteTypes;
/// <summary>
/// The disposable parts
/// The disposable parts.
/// </summary>
private readonly List<IDisposable> _disposableParts = new List<IDisposable>();
@@ -236,11 +237,6 @@ namespace Emby.Server.Implementations
/// <value>The server configuration manager.</value>
public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager;
protected virtual IResourceFileManager CreateResourceFileManager()
{
return new ResourceFileManager(HttpResultFactory, LoggerFactory, FileSystemManager);
}
/// <summary>
/// Gets or sets the user manager.
/// </summary>
@@ -299,8 +295,6 @@ namespace Emby.Server.Implementations
/// <value>The user data repository.</value>
private IUserDataManager UserDataManager { get; set; }
private IUserRepository UserRepository { get; set; }
internal SqliteItemRepository ItemRepository { get; set; }
private INotificationManager NotificationManager { get; set; }
@@ -321,8 +315,6 @@ namespace Emby.Server.Implementations
private IMediaSourceManager MediaSourceManager { get; set; }
private IPlaylistManager PlaylistManager { get; set; }
private readonly IConfiguration _configuration;
/// <summary>
@@ -331,14 +323,6 @@ namespace Emby.Server.Implementations
/// <value>The installation manager.</value>
protected IInstallationManager InstallationManager { get; private set; }
/// <summary>
/// Gets or sets the zip client.
/// </summary>
/// <value>The zip client.</value>
protected IZipClient ZipClient { get; private set; }
protected IHttpResultFactory HttpResultFactory { get; private set; }
protected IAuthService AuthService { get; private set; }
public IStartupOptions StartupOptions { get; }
@@ -394,7 +378,7 @@ namespace Emby.Server.Implementations
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
NetworkManager.NetworkChanged += NetworkManager_NetworkChanged;
NetworkManager.NetworkChanged += OnNetworkChanged;
}
public string ExpandVirtualPath(string path)
@@ -418,7 +402,7 @@ namespace Emby.Server.Implementations
return ServerConfigurationManager.Configuration.LocalNetworkSubnets;
}
private void NetworkManager_NetworkChanged(object sender, EventArgs e)
private void OnNetworkChanged(object sender, EventArgs e)
{
_validAddressResults.Clear();
}
@@ -426,10 +410,10 @@ namespace Emby.Server.Implementations
public string ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
/// <summary>
/// Gets the current application user agent
/// Gets the current application user agent.
/// </summary>
/// <value>The application user agent.</value>
public string ApplicationUserAgent => Name.Replace(' ','-') + "/" + ApplicationVersion;
public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersion;
/// <summary>
/// Gets the email address for use within a comment section of a user agent field.
@@ -437,14 +421,11 @@ namespace Emby.Server.Implementations
/// </summary>
public string ApplicationUserAgentAddress { get; } = "team@jellyfin.org";
private string _productName;
/// <summary>
/// Gets the current application name
/// Gets the current application name.
/// </summary>
/// <value>The application name.</value>
public string ApplicationProductName
=> _productName ?? (_productName = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName);
public string ApplicationProductName { get; } = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName;
private DeviceId _deviceId;
@@ -478,8 +459,8 @@ namespace Emby.Server.Implementations
/// <summary>
/// Creates an instance of type and resolves all constructor dependencies
/// </summary>
/// /// <typeparam name="T">The type</typeparam>
/// <returns>T</returns>
/// /// <typeparam name="T">The type.</typeparam>
/// <returns>T.</returns>
public T CreateInstance<T>()
=> ActivatorUtilities.CreateInstance<T>(_serviceProvider);
@@ -518,16 +499,11 @@ namespace Emby.Server.Implementations
{
var currentType = typeof(T);
return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i));
return _allConcreteTypes.Where(i => currentType.IsAssignableFrom(i));
}
/// <summary>
/// Gets the exports.
/// </summary>
/// <typeparam name="T">The type</typeparam>
/// <param name="manageLifetime">if set to <c>true</c> [manage lifetime].</param>
/// <returns>IEnumerable{``0}.</returns>
public IEnumerable<T> GetExports<T>(bool manageLifetime = true)
/// <inheritdoc />
public IReadOnlyCollection<T> GetExports<T>(bool manageLifetime = true)
{
var parts = GetExportTypes<T>()
.Select(CreateInstanceSafe)
@@ -549,6 +525,7 @@ namespace Emby.Server.Implementations
/// <summary>
/// Runs the startup tasks.
/// </summary>
/// <returns><see cref="Task" />.</returns>
public async Task RunStartupTasksAsync()
{
Logger.LogInformation("Running startup tasks");
@@ -561,7 +538,7 @@ namespace Emby.Server.Implementations
Logger.LogInformation("ServerId: {0}", SystemId);
var entryPoints = GetExports<IServerEntryPoint>().ToList();
var entryPoints = GetExports<IServerEntryPoint>();
var stopWatch = new Stopwatch();
stopWatch.Start();
@@ -612,16 +589,19 @@ namespace Emby.Server.Implementations
foreach (var plugin in Plugins)
{
pluginBuilder.AppendLine(string.Format("{0} {1}", plugin.Name, plugin.Version));
pluginBuilder.AppendLine(
string.Format(
CultureInfo.InvariantCulture,
"{0} {1}",
plugin.Name,
plugin.Version));
}
Logger.LogInformation("Plugins: {plugins}", pluginBuilder.ToString());
Logger.LogInformation("Plugins: {Plugins}", pluginBuilder.ToString());
}
DiscoverTypes();
SetHttpLimit();
await RegisterResources(serviceCollection).ConfigureAwait(false);
FindParts();
@@ -635,11 +615,34 @@ namespace Emby.Server.Implementations
var host = new WebHostBuilder()
.UseKestrel(options =>
{
options.ListenAnyIP(HttpPort);
if (EnableHttps && Certificate != null)
var addresses = ServerConfigurationManager
.Configuration
.LocalNetworkAddresses
.Select(NormalizeConfiguredLocalAddress)
.Where(i => i != null)
.ToList();
if (addresses.Any())
{
options.ListenAnyIP(HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); });
foreach (var address in addresses)
{
Logger.LogInformation("Kestrel listening on {ipaddr}", address);
options.Listen(address, HttpPort);
if (EnableHttps && Certificate != null)
{
options.Listen(address, HttpsPort, listenOptions => listenOptions.UseHttps(Certificate));
}
}
}
else
{
Logger.LogInformation("Kestrel listening on all interfaces");
options.ListenAnyIP(HttpPort);
if (EnableHttps && Certificate != null)
{
options.ListenAnyIP(HttpsPort, listenOptions => listenOptions.UseHttps(Certificate));
}
}
})
.UseContentRoot(contentRoot)
@@ -653,13 +656,22 @@ namespace Emby.Server.Implementations
app.UseWebSockets();
app.UseResponseCompression();
// TODO app.UseMiddleware<WebSocketMiddleware>();
app.Use(ExecuteWebsocketHandlerAsync);
app.Use(ExecuteHttpHandlerAsync);
})
.Build();
await host.StartAsync().ConfigureAwait(false);
try
{
await host.StartAsync().ConfigureAwait(false);
}
catch
{
Logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in system.xml and try again.");
throw;
}
}
private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
@@ -686,16 +698,9 @@ namespace Emby.Server.Implementations
var localPath = context.Request.Path.ToString();
var req = new WebSocketSharpRequest(request, response, request.Path, Logger);
await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false);
await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
}
protected virtual IHttpClient CreateHttpClient()
{
return new HttpClientManager.HttpClientManager(ApplicationPaths, LoggerFactory, FileSystemManager, () => ApplicationUserAgent);
}
public static IStreamHelper StreamHelper { get; set; }
/// <summary>
/// Registers resources that classes will depend on
/// </summary>
@@ -719,7 +724,11 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(FileSystemManager);
serviceCollection.AddSingleton<TvDbClientManager>();
HttpClient = CreateHttpClient();
HttpClient = new HttpClientManager.HttpClientManager(
ApplicationPaths,
LoggerFactory.CreateLogger<HttpClientManager.HttpClientManager>(),
FileSystemManager,
() => ApplicationUserAgent);
serviceCollection.AddSingleton(HttpClient);
serviceCollection.AddSingleton(NetworkManager);
@@ -735,28 +744,26 @@ namespace Emby.Server.Implementations
ProcessFactory = new ProcessFactory();
serviceCollection.AddSingleton(ProcessFactory);
ApplicationHost.StreamHelper = new StreamHelper();
serviceCollection.AddSingleton(StreamHelper);
serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
serviceCollection.AddSingleton(typeof(ICryptoProvider), typeof(CryptographyProvider));
var cryptoProvider = new CryptographyProvider();
serviceCollection.AddSingleton<ICryptoProvider>(cryptoProvider);
SocketFactory = new SocketFactory();
serviceCollection.AddSingleton(SocketFactory);
serviceCollection.AddSingleton(typeof(IInstallationManager), typeof(InstallationManager));
ZipClient = new ZipClient();
serviceCollection.AddSingleton(ZipClient);
serviceCollection.AddSingleton(typeof(IZipClient), typeof(ZipClient));
HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, StreamHelper);
serviceCollection.AddSingleton(HttpResultFactory);
serviceCollection.AddSingleton(typeof(IHttpResultFactory), typeof(HttpResultFactory));
serviceCollection.AddSingleton<IServerApplicationHost>(this);
serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
serviceCollection.AddSingleton(ServerConfigurationManager);
LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, LoggerFactory);
LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, LoggerFactory.CreateLogger<LocalizationManager>());
await LocalizationManager.LoadAll().ConfigureAwait(false);
serviceCollection.AddSingleton<ILocalizationManager>(LocalizationManager);
@@ -765,12 +772,12 @@ namespace Emby.Server.Implementations
UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager);
serviceCollection.AddSingleton(UserDataManager);
UserRepository = GetUserRepository();
// This is only needed for disposal purposes. If removing this, make sure to have the manager handle disposing it
serviceCollection.AddSingleton(UserRepository);
var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LoggerFactory, JsonSerializer, ApplicationPaths, FileSystemManager);
serviceCollection.AddSingleton<IDisplayPreferencesRepository>(displayPreferencesRepo);
_displayPreferencesRepository = new SqliteDisplayPreferencesRepository(
LoggerFactory.CreateLogger<SqliteDisplayPreferencesRepository>(),
JsonSerializer,
ApplicationPaths,
FileSystemManager);
serviceCollection.AddSingleton<IDisplayPreferencesRepository>(_displayPreferencesRepository);
ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, LocalizationManager);
serviceCollection.AddSingleton<IItemRepository>(ItemRepository);
@@ -778,7 +785,20 @@ namespace Emby.Server.Implementations
AuthenticationRepository = GetAuthenticationRepository();
serviceCollection.AddSingleton(AuthenticationRepository);
UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager);
_userRepository = GetUserRepository();
UserManager = new UserManager(
LoggerFactory.CreateLogger<UserManager>(),
_userRepository,
XmlSerializer,
NetworkManager,
() => ImageProcessor,
() => DtoService,
this,
JsonSerializer,
FileSystemManager,
cryptoProvider);
serviceCollection.AddSingleton(UserManager);
LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager);
@@ -798,7 +818,7 @@ namespace Emby.Server.Implementations
HttpServer = new HttpListenerHost(
this,
LoggerFactory,
LoggerFactory.CreateLogger<HttpListenerHost>(),
ServerConfigurationManager,
_configuration,
NetworkManager,
@@ -811,14 +831,13 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(HttpServer);
ImageProcessor = GetImageProcessor();
ImageProcessor = new ImageProcessor(LoggerFactory.CreateLogger<ImageProcessor>(), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder);
serviceCollection.AddSingleton(ImageProcessor);
TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager);
serviceCollection.AddSingleton(TVSeriesManager);
DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager);
serviceCollection.AddSingleton(DeviceManager);
MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder);
@@ -833,10 +852,10 @@ namespace Emby.Server.Implementations
DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager);
serviceCollection.AddSingleton(DtoService);
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager);
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager);
serviceCollection.AddSingleton(ChannelManager);
SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager);
SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, this, AuthenticationRepository, DeviceManager, MediaSourceManager);
serviceCollection.AddSingleton(SessionManager);
serviceCollection.AddSingleton<IDlnaManager>(
@@ -845,8 +864,7 @@ namespace Emby.Server.Implementations
CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager);
serviceCollection.AddSingleton(CollectionManager);
PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager);
serviceCollection.AddSingleton(PlaylistManager);
serviceCollection.AddSingleton(typeof(IPlaylistManager), typeof(PlaylistManager));
LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager);
serviceCollection.AddSingleton(LiveTvManager);
@@ -887,15 +905,15 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
AuthService = new AuthService(UserManager, authContext, ServerConfigurationManager, SessionManager, NetworkManager);
AuthService = new AuthService(authContext, ServerConfigurationManager, SessionManager, NetworkManager);
serviceCollection.AddSingleton(AuthService);
SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory);
serviceCollection.AddSingleton(SubtitleEncoder);
serviceCollection.AddSingleton(CreateResourceFileManager());
serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager));
displayPreferencesRepo.Initialize();
_displayPreferencesRepository.Initialize();
var userDataRepo = new SqliteUserDataRepository(LoggerFactory, ApplicationPaths);
@@ -918,8 +936,7 @@ namespace Emby.Server.Implementations
.Distinct();
logger.LogInformation("Arguments: {Args}", commandLineArgs);
// FIXME: @bond this logs the kernel version, not the OS version
logger.LogInformation("Operating system: {OS} {OSVersion}", OperatingSystem.Name, Environment.OSVersion.Version);
logger.LogInformation("Operating system: {OS}", OperatingSystem.Name);
logger.LogInformation("Architecture: {Architecture}", RuntimeInformation.OSArchitecture);
logger.LogInformation("64-Bit Process: {Is64Bit}", Environment.Is64BitProcess);
logger.LogInformation("User Interactive: {IsUserInteractive}", Environment.UserInteractive);
@@ -929,19 +946,6 @@ namespace Emby.Server.Implementations
logger.LogInformation("Application directory: {ApplicationPath}", appPaths.ProgramSystemPath);
}
private void SetHttpLimit()
{
try
{
// Increase the max http request limit
ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
}
catch (Exception ex)
{
Logger.LogError(ex, "Error setting http limit");
}
}
private X509Certificate2 GetCertificate(CertificateInfo info)
{
var certificateLocation = info?.Path;
@@ -978,18 +982,16 @@ namespace Emby.Server.Implementations
}
}
private IImageProcessor GetImageProcessor()
{
return new ImageProcessor(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder);
}
/// <summary>
/// Gets the user repository.
/// </summary>
/// <returns>Task{IUserRepository}.</returns>
private IUserRepository GetUserRepository()
/// <returns><see cref="Task{SqliteUserRepository}" />.</returns>
private SqliteUserRepository GetUserRepository()
{
var repo = new SqliteUserRepository(LoggerFactory, ApplicationPaths, JsonSerializer);
var repo = new SqliteUserRepository(
LoggerFactory.CreateLogger<SqliteUserRepository>(),
ApplicationPaths,
JsonSerializer);
repo.Initialize();
@@ -1035,7 +1037,6 @@ namespace Emby.Server.Implementations
Video.LiveTvManager = LiveTvManager;
Folder.UserViewManager = UserViewManager;
UserView.TVSeriesManager = TVSeriesManager;
UserView.PlaylistManager = PlaylistManager;
UserView.CollectionManager = CollectionManager;
BaseItem.MediaSourceManager = MediaSourceManager;
CollectionFolder.XmlSerializer = XmlSerializer;
@@ -1051,9 +1052,11 @@ namespace Emby.Server.Implementations
.Select(x => Assembly.LoadFrom(x))
.SelectMany(x => x.ExportedTypes)
.Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && !x.IsGenericType)
.ToList();
.ToArray();
types.AddRange(types);
int oldLen = _allConcreteTypes.Length;
Array.Resize(ref _allConcreteTypes, oldLen + types.Length);
types.CopyTo(_allConcreteTypes, oldLen);
var plugins = types.Where(x => x.IsAssignableFrom(typeof(IPlugin)))
.Select(CreateInstanceSafe)
@@ -1063,8 +1066,8 @@ namespace Emby.Server.Implementations
.Where(x => x != null)
.ToArray();
int oldLen = _plugins.Length;
Array.Resize<IPlugin>(ref _plugins, _plugins.Length + plugins.Length);
oldLen = _plugins.Length;
Array.Resize(ref _plugins, oldLen + plugins.Length);
plugins.CopyTo(_plugins, oldLen);
var entries = types.Where(x => x.IsAssignableFrom(typeof(IServerEntryPoint)))
@@ -1073,8 +1076,8 @@ namespace Emby.Server.Implementations
.Cast<IServerEntryPoint>()
.ToList();
await Task.WhenAll(StartEntryPoints(entries, true));
await Task.WhenAll(StartEntryPoints(entries, false));
await Task.WhenAll(StartEntryPoints(entries, true)).ConfigureAwait(false);
await Task.WhenAll(StartEntryPoints(entries, false)).ConfigureAwait(false);
}
/// <summary>
@@ -1113,7 +1116,7 @@ namespace Emby.Server.Implementations
GetExports<IMetadataSaver>(),
GetExports<IExternalId>());
ImageProcessor.AddParts(GetExports<IImageEnhancer>());
ImageProcessor.ImageEnhancers = GetExports<IImageEnhancer>();
LiveTvManager.AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
@@ -1167,7 +1170,7 @@ namespace Emby.Server.Implementations
}
catch (Exception ex)
{
Logger.LogError(ex, "Error loading plugin {pluginName}", plugin.GetType().FullName);
Logger.LogError(ex, "Error loading plugin {PluginName}", plugin.GetType().FullName);
return null;
}
@@ -1181,10 +1184,32 @@ namespace Emby.Server.Implementations
{
Logger.LogInformation("Loading assemblies");
AllConcreteTypes = GetComposablePartAssemblies()
.SelectMany(x => x.ExportedTypes)
.Where(type => type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType)
.ToArray();
_allConcreteTypes = GetTypes(GetComposablePartAssemblies()).ToArray();
}
private IEnumerable<Type> GetTypes(IEnumerable<Assembly> assemblies)
{
foreach (var ass in assemblies)
{
Type[] exportedTypes;
try
{
exportedTypes = ass.GetExportedTypes();
}
catch (TypeLoadException ex)
{
Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName);
continue;
}
foreach (Type type in exportedTypes)
{
if (type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType)
{
yield return type;
}
}
}
}
private CertificateInfo CertificateInfo { get; set; }
@@ -1215,25 +1240,11 @@ namespace Emby.Server.Implementations
private CertificateInfo GetCertificateInfo(bool generateCertificate)
{
if (!string.IsNullOrWhiteSpace(ServerConfigurationManager.Configuration.CertificatePath))
{
// Custom cert
return new CertificateInfo
{
Path = ServerConfigurationManager.Configuration.CertificatePath,
Password = ServerConfigurationManager.Configuration.CertificatePassword
};
}
// Generate self-signed cert
var certHost = GetHostnameFromExternalDns(ServerConfigurationManager.Configuration.WanDdns);
var certPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.ProgramDataPath, "ssl", "cert_" + (certHost + "2").GetMD5().ToString("N") + ".pfx");
const string Password = "embycert";
// Custom cert
return new CertificateInfo
{
Path = certPath,
Password = Password
Path = ServerConfigurationManager.Configuration.CertificatePath,
Password = ServerConfigurationManager.Configuration.CertificatePassword
};
}
@@ -1348,8 +1359,19 @@ namespace Emby.Server.Implementations
{
foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.AllDirectories))
{
Logger.LogInformation("Loading assembly {Path}", file);
yield return Assembly.LoadFrom(file);
Assembly plugAss;
try
{
plugAss = Assembly.LoadFrom(file);
}
catch (FileLoadException ex)
{
Logger.LogError(ex, "Failed to load assembly {Path}", file);
continue;
}
Logger.LogInformation("Loaded assembly {Assembly} from {Path}", plugAss.FullName, file);
yield return plugAss;
}
}
@@ -1408,24 +1430,12 @@ namespace Emby.Server.Implementations
public async Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken)
{
var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
string wanAddress;
if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns))
{
wanAddress = await GetWanApiUrlFromExternal(cancellationToken).ConfigureAwait(false);
}
else
{
wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns);
}
return new SystemInfo
{
HasPendingRestart = HasPendingRestart,
IsShuttingDown = IsShuttingDown,
Version = ApplicationVersion,
ProductName = ApplicationProductName,
WebSocketPortNumber = HttpPort,
CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(),
Id = SystemId,
@@ -1442,7 +1452,6 @@ namespace Emby.Server.Implementations
OperatingSystemDisplayName = OperatingSystem.Name,
CanSelfRestart = CanSelfRestart,
CanLaunchWebBrowser = CanLaunchWebBrowser,
WanAddress = wanAddress,
HasUpdateAvailable = HasUpdateAvailable,
TranscodingTempPath = ApplicationPaths.TranscodingTempPath,
ServerName = FriendlyName,
@@ -1455,36 +1464,21 @@ namespace Emby.Server.Implementations
};
}
public WakeOnLanInfo[] GetWakeOnLanInfo()
{
return NetworkManager.GetMacAddresses()
.Select(i => new WakeOnLanInfo
{
MacAddress = i
})
.ToArray();
}
public IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo()
=> NetworkManager.GetMacAddresses()
.Select(i => new WakeOnLanInfo(i))
.ToList();
public async Task<PublicSystemInfo> GetPublicSystemInfo(CancellationToken cancellationToken)
{
var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
string wanAddress;
if (string.IsNullOrEmpty(ServerConfigurationManager.Configuration.WanDdns))
{
wanAddress = await GetWanApiUrlFromExternal(cancellationToken).ConfigureAwait(false);
}
else
{
wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns);
}
var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
return new PublicSystemInfo
{
Version = ApplicationVersion,
ProductName = ApplicationProductName,
Id = SystemId,
OperatingSystem = OperatingSystem.Id.ToString(),
WanAddress = wanAddress,
ServerName = FriendlyName,
LocalAddress = localAddress
};
@@ -1516,40 +1510,32 @@ namespace Emby.Server.Implementations
return null;
}
public async Task<string> GetWanApiUrlFromExternal(CancellationToken cancellationToken)
/// <summary>
/// Removes the scope id from IPv6 addresses.
/// </summary>
/// <param name="address">The IPv6 address.</param>
/// <returns>The IPv6 address without the scope id.</returns>
private string RemoveScopeId(string address)
{
const string Url = "http://ipv4.icanhazip.com";
try
var index = address.IndexOf('%');
if (index == -1)
{
using (var response = await HttpClient.Get(new HttpRequestOptions
{
Url = Url,
LogErrorResponseBody = false,
LogErrors = false,
LogRequest = false,
TimeoutMs = 10000,
BufferContent = false,
CancellationToken = cancellationToken
}).ConfigureAwait(false))
{
return GetWanApiUrl(response.ReadToEnd().Trim());
}
return address;
}
catch (Exception ex)
{
Logger.LogError(ex, "Error getting WAN Ip address information");
}
return null;
return address.Substring(0, index);
}
public string GetLocalApiUrl(IpAddressInfo ipAddress)
public string GetLocalApiUrl(IPAddress ipAddress)
{
if (ipAddress.AddressFamily == IpAddressFamily.InterNetworkV6)
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
{
return GetLocalApiUrl("[" + ipAddress.Address + "]");
var str = RemoveScopeId(ipAddress.ToString());
return GetLocalApiUrl("[" + str + "]");
}
return GetLocalApiUrl(ipAddress.Address);
return GetLocalApiUrl(ipAddress.ToString());
}
public string GetLocalApiUrl(string host)
@@ -1560,40 +1546,18 @@ namespace Emby.Server.Implementations
host,
HttpsPort.ToString(CultureInfo.InvariantCulture));
}
return string.Format("http://{0}:{1}",
host,
HttpPort.ToString(CultureInfo.InvariantCulture));
}
public string GetWanApiUrl(IpAddressInfo ipAddress)
{
if (ipAddress.AddressFamily == IpAddressFamily.InterNetworkV6)
{
return GetWanApiUrl("[" + ipAddress.Address + "]");
}
return GetWanApiUrl(ipAddress.Address);
}
public string GetWanApiUrl(string host)
{
if (EnableHttps)
{
return string.Format("https://{0}:{1}",
host,
ServerConfigurationManager.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture));
}
return string.Format("http://{0}:{1}",
host,
ServerConfigurationManager.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture));
}
public Task<List<IpAddressInfo>> GetLocalIpAddresses(CancellationToken cancellationToken)
public Task<List<IPAddress>> GetLocalIpAddresses(CancellationToken cancellationToken)
{
return GetLocalIpAddressesInternal(true, 0, cancellationToken);
}
private async Task<List<IpAddressInfo>> GetLocalIpAddressesInternal(bool allowLoopback, int limit, CancellationToken cancellationToken)
private async Task<List<IPAddress>> GetLocalIpAddressesInternal(bool allowLoopback, int limit, CancellationToken cancellationToken)
{
var addresses = ServerConfigurationManager
.Configuration
@@ -1607,13 +1571,13 @@ namespace Emby.Server.Implementations
addresses.AddRange(NetworkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
}
var resultList = new List<IpAddressInfo>();
var resultList = new List<IPAddress>();
foreach (var address in addresses)
{
if (!allowLoopback)
{
if (address.Equals(IpAddressInfo.Loopback) || address.Equals(IpAddressInfo.IPv6Loopback))
if (address.Equals(IPAddress.Loopback) || address.Equals(IPAddress.IPv6Loopback))
{
continue;
}
@@ -1634,7 +1598,7 @@ namespace Emby.Server.Implementations
return resultList;
}
private IpAddressInfo NormalizeConfiguredLocalAddress(string address)
private IPAddress NormalizeConfiguredLocalAddress(string address)
{
var index = address.Trim('/').IndexOf('/');
@@ -1643,7 +1607,7 @@ namespace Emby.Server.Implementations
address = address.Substring(index + 1);
}
if (NetworkManager.TryParseIpAddress(address.Trim('/'), out IpAddressInfo result))
if (IPAddress.TryParse(address.Trim('/'), out IPAddress result))
{
return result;
}
@@ -1653,10 +1617,10 @@ namespace Emby.Server.Implementations
private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
private async Task<bool> IsIpAddressValidAsync(IpAddressInfo address, CancellationToken cancellationToken)
private async Task<bool> IsIpAddressValidAsync(IPAddress address, CancellationToken cancellationToken)
{
if (address.Equals(IpAddressInfo.Loopback) ||
address.Equals(IpAddressInfo.IPv6Loopback))
if (address.Equals(IPAddress.Loopback)
|| address.Equals(IPAddress.IPv6Loopback))
{
return true;
}
@@ -1669,12 +1633,6 @@ namespace Emby.Server.Implementations
return cachedResult;
}
#if DEBUG
const bool LogPing = true;
#else
const bool LogPing = false;
#endif
try
{
using (var response = await HttpClient.SendAsync(
@@ -1682,17 +1640,13 @@ namespace Emby.Server.Implementations
{
Url = apiUrl,
LogErrorResponseBody = false,
LogErrors = LogPing,
LogRequest = LogPing,
TimeoutMs = 5000,
BufferContent = false,
CancellationToken = cancellationToken
}, "POST").ConfigureAwait(false))
}, HttpMethod.Post).ConfigureAwait(false))
{
using (var reader = new StreamReader(response.Content))
{
var result = reader.ReadToEnd();
var result = await reader.ReadToEndAsync().ConfigureAwait(false);
var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase);
_validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid);
@@ -1841,24 +1795,6 @@ namespace Emby.Server.Implementations
{
}
/// <summary>
/// Called when [application updated].
/// </summary>
/// <param name="package">The package.</param>
protected void OnApplicationUpdated(PackageVersionInfo package)
{
Logger.LogInformation("Application has been updated to version {0}", package.versionStr);
ApplicationUpdated?.Invoke(
this,
new GenericEventArgs<PackageVersionInfo>()
{
Argument = package
});
NotifyPendingRestart();
}
private bool _disposed = false;
/// <summary>
@@ -1903,8 +1839,14 @@ namespace Emby.Server.Implementations
Logger.LogError(ex, "Error disposing {Type}", part.GetType().Name);
}
}
_userRepository?.Dispose();
_displayPreferencesRepository.Dispose();
}
_userRepository = null;
_displayPreferencesRepository = null;
_disposed = true;
}
}

View File

@@ -1,12 +1,12 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
@@ -20,7 +20,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization;
@@ -40,11 +39,8 @@ namespace Emby.Server.Implementations.Channels
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;
private readonly IProviderManager _providerManager;
private readonly ILocalizationManager _localization;
public ChannelManager(
IUserManager userManager,
IDtoService dtoService,
@@ -54,8 +50,6 @@ namespace Emby.Server.Implementations.Channels
IFileSystem fileSystem,
IUserDataManager userDataManager,
IJsonSerializer jsonSerializer,
ILocalizationManager localization,
IHttpClient httpClient,
IProviderManager providerManager)
{
_userManager = userManager;
@@ -66,8 +60,6 @@ namespace Emby.Server.Implementations.Channels
_fileSystem = fileSystem;
_userDataManager = userDataManager;
_jsonSerializer = jsonSerializer;
_localization = localization;
_httpClient = httpClient;
_providerManager = providerManager;
}
@@ -215,7 +207,7 @@ namespace Emby.Server.Implementations.Channels
try
{
return GetChannelProvider(i).IsEnabledFor(user.Id.ToString("N"));
return GetChannelProvider(i).IsEnabledFor(user.Id.ToString("N", CultureInfo.InvariantCulture));
}
catch
{
@@ -520,7 +512,7 @@ namespace Emby.Server.Implementations.Channels
IncludeItemTypes = new[] { typeof(Channel).Name },
OrderBy = new ValueTuple<string, SortOrder>[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) }
}).Select(i => GetChannelFeatures(i.ToString("N"))).ToArray();
}).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
}
public ChannelFeatures GetChannelFeatures(string id)
@@ -561,7 +553,7 @@ namespace Emby.Server.Implementations.Channels
SupportsSortOrderToggle = features.SupportsSortOrderToggle,
SupportsLatestMedia = supportsLatest,
Name = channel.Name,
Id = channel.Id.ToString("N"),
Id = channel.Id.ToString("N", CultureInfo.InvariantCulture),
SupportsContentDownloading = features.SupportsContentDownloading,
AutoRefreshLevels = features.AutoRefreshLevels
};
@@ -749,7 +741,7 @@ namespace Emby.Server.Implementations.Channels
bool sortDescending,
CancellationToken cancellationToken)
{
var userId = user == null ? null : user.Id.ToString("N");
var userId = user == null ? null : user.Id.ToString("N", CultureInfo.InvariantCulture);
var cacheLength = CacheLength;
var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
@@ -845,7 +837,7 @@ namespace Emby.Server.Implementations.Channels
ChannelItemSortField? sortField,
bool sortDescending)
{
var channelId = GetInternalChannelId(channel.Name).ToString("N");
var channelId = GetInternalChannelId(channel.Name).ToString("N", CultureInfo.InvariantCulture);
var userCacheKey = string.Empty;
@@ -855,10 +847,10 @@ namespace Emby.Server.Implementations.Channels
userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty;
}
var filename = string.IsNullOrEmpty(externalFolderId) ? "root" : externalFolderId.GetMD5().ToString("N");
var filename = string.IsNullOrEmpty(externalFolderId) ? "root" : externalFolderId.GetMD5().ToString("N", CultureInfo.InvariantCulture);
filename += userCacheKey;
var version = ((channel.DataVersion ?? string.Empty) + "2").GetMD5().ToString("N");
var version = ((channel.DataVersion ?? string.Empty) + "2").GetMD5().ToString("N", CultureInfo.InvariantCulture);
if (sortField.HasValue)
{
@@ -869,7 +861,7 @@ namespace Emby.Server.Implementations.Channels
filename += "-sortDescending";
}
filename = filename.GetMD5().ToString("N");
filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture);
return Path.Combine(_config.ApplicationPaths.CachePath,
"channels",

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
@@ -182,7 +183,7 @@ namespace Emby.Server.Implementations.Collections
public void AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
{
AddToCollection(collectionId, ids.Select(i => i.ToString("N")), true, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)));
AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)));
}
private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)

View File

@@ -66,7 +66,7 @@ namespace Emby.Server.Implementations.Configuration
{
base.AddParts(factories);
UpdateTranscodingTempPath();
UpdateTranscodePath();
}
/// <summary>
@@ -74,35 +74,26 @@ namespace Emby.Server.Implementations.Configuration
/// </summary>
private void UpdateMetadataPath()
{
string metadataPath;
if (string.IsNullOrWhiteSpace(Configuration.MetadataPath))
{
metadataPath = GetInternalMetadataPath();
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = Path.Combine(ApplicationPaths.ProgramDataPath, "metadata");
}
else
{
metadataPath = Path.Combine(Configuration.MetadataPath, "metadata");
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = Configuration.MetadataPath;
}
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = metadataPath;
}
private string GetInternalMetadataPath()
{
return Path.Combine(ApplicationPaths.ProgramDataPath, "metadata");
}
/// <summary>
/// Updates the transcoding temporary path.
/// </summary>
private void UpdateTranscodingTempPath()
private void UpdateTranscodePath()
{
var encodingConfig = this.GetConfiguration<EncodingOptions>("encoding");
((ServerApplicationPaths)ApplicationPaths).TranscodingTempPath = string.IsNullOrEmpty(encodingConfig.TranscodingTempPath) ?
null :
Path.Combine(encodingConfig.TranscodingTempPath, "transcoding-temp");
Path.Combine(encodingConfig.TranscodingTempPath, "transcodes");
}
protected override void OnNamedConfigurationUpdated(string key, object configuration)
@@ -111,7 +102,7 @@ namespace Emby.Server.Implementations.Configuration
if (string.Equals(key, "encoding", StringComparison.OrdinalIgnoreCase))
{
UpdateTranscodingTempPath();
UpdateTranscodePath();
}
}

View File

@@ -6,8 +6,8 @@ namespace Emby.Server.Implementations
{
public static readonly Dictionary<string, string> Configuration = new Dictionary<string, string>
{
{"HttpListenerHost:DefaultRedirectPath", "web/index.html"},
{"MusicBrainz:BaseUrl", "https://www.musicbrainz.org"}
{ "HttpListenerHost:DefaultRedirectPath", "web/index.html" },
{ "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" }
};
}
}

View File

@@ -1,14 +1,12 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using MediaBrowser.Model.Cryptography;
using static MediaBrowser.Common.Cryptography.Constants;
namespace Emby.Server.Implementations.Cryptography
{
public class CryptographyProvider : ICryptoProvider
public class CryptographyProvider : ICryptoProvider, IDisposable
{
private static readonly HashSet<string> _supportedHashMethods = new HashSet<string>()
{
@@ -28,59 +26,28 @@ namespace Emby.Server.Implementations.Cryptography
"System.Security.Cryptography.SHA512"
};
public string DefaultHashMethod => "PBKDF2";
private RandomNumberGenerator _randomNumberGenerator;
private const int _defaultIterations = 1000;
private bool _disposed = false;
public CryptographyProvider()
{
//FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
//Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1
//there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one
//Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1
// FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
// Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1
// there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one
// Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1
_randomNumberGenerator = RandomNumberGenerator.Create();
}
public Guid GetMD5(string str)
{
return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str)));
}
public byte[] ComputeSHA1(byte[] bytes)
{
using (var provider = SHA1.Create())
{
return provider.ComputeHash(bytes);
}
}
public byte[] ComputeMD5(Stream str)
{
using (var provider = MD5.Create())
{
return provider.ComputeHash(str);
}
}
public byte[] ComputeMD5(byte[] bytes)
{
using (var provider = MD5.Create())
{
return provider.ComputeHash(bytes);
}
}
public string DefaultHashMethod => "PBKDF2";
public IEnumerable<string> GetSupportedHashMethods()
{
return _supportedHashMethods;
}
=> _supportedHashMethods;
private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations)
{
//downgrading for now as we need this library to be dotnetstandard compliant
//with this downgrade we'll add a check to make sure we're on the downgrade method at the moment
// downgrading for now as we need this library to be dotnetstandard compliant
// with this downgrade we'll add a check to make sure we're on the downgrade method at the moment
if (method == DefaultHashMethod)
{
using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations))
@@ -93,20 +60,16 @@ namespace Emby.Server.Implementations.Cryptography
}
public byte[] ComputeHash(string hashMethod, byte[] bytes)
{
return ComputeHash(hashMethod, bytes, Array.Empty<byte>());
}
=> ComputeHash(hashMethod, bytes, Array.Empty<byte>());
public byte[] ComputeHashWithDefaultMethod(byte[] bytes)
{
return ComputeHash(DefaultHashMethod, bytes);
}
=> ComputeHash(DefaultHashMethod, bytes);
public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt)
{
if (hashMethod == DefaultHashMethod)
{
return PBKDF2(hashMethod, bytes, salt, _defaultIterations);
return PBKDF2(hashMethod, bytes, salt, DefaultIterations);
}
else if (_supportedHashMethods.Contains(hashMethod))
{
@@ -125,44 +88,46 @@ namespace Emby.Server.Implementations.Cryptography
}
}
}
else
{
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
}
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
}
public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
{
return PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations);
}
public byte[] ComputeHash(PasswordHash hash)
{
int iterations = _defaultIterations;
if (!hash.Parameters.ContainsKey("iterations"))
{
hash.Parameters.Add("iterations", _defaultIterations.ToString(CultureInfo.InvariantCulture));
}
else
{
try
{
iterations = int.Parse(hash.Parameters["iterations"]);
}
catch (Exception e)
{
throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e);
}
}
return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations);
}
=> PBKDF2(DefaultHashMethod, bytes, salt, DefaultIterations);
public byte[] GenerateSalt()
=> GenerateSalt(DefaultSaltLength);
public byte[] GenerateSalt(int length)
{
byte[] salt = new byte[64];
byte[] salt = new byte[length];
_randomNumberGenerator.GetBytes(salt);
return salt;
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
_randomNumberGenerator.Dispose();
}
_randomNumberGenerator = null;
_disposed = true;
}
}
}

View File

@@ -1,183 +1,144 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using Microsoft.Extensions.Logging;
using SQLitePCL;
using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Data
{
public abstract class BaseSqliteRepository : IDisposable
{
protected string DbFilePath { get; set; }
protected ReaderWriterLockSlim WriteLock;
protected ILogger Logger { get; private set; }
private bool _disposed = false;
protected BaseSqliteRepository(ILogger logger)
{
Logger = logger;
WriteLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
}
/// <summary>
/// Gets or sets the path to the DB file.
/// </summary>
/// <value>Path to the DB file.</value>
protected string DbFilePath { get; set; }
/// <summary>
/// Gets the logger.
/// </summary>
/// <value>The logger.</value>
protected ILogger Logger { get; }
/// <summary>
/// Gets the default connection flags.
/// </summary>
/// <value>The default connection flags.</value>
protected virtual ConnectionFlags DefaultConnectionFlags => ConnectionFlags.NoMutex;
/// <summary>
/// Gets the transaction mode.
/// </summary>
/// <value>The transaction mode.</value>>
protected TransactionMode TransactionMode => TransactionMode.Deferred;
/// <summary>
/// Gets the transaction mode for read-only operations.
/// </summary>
/// <value>The transaction mode.</value>
protected TransactionMode ReadTransactionMode => TransactionMode.Deferred;
internal static int ThreadSafeMode { get; set; }
/// <summary>
/// Gets the cache size.
/// </summary>
/// <value>The cache size or null.</value>
protected virtual int? CacheSize => null;
static BaseSqliteRepository()
/// <summary>
/// Gets the journal mode. <see href="https://www.sqlite.org/pragma.html#pragma_journal_mode" />
/// </summary>
/// <value>The journal mode.</value>
protected virtual string JournalMode => "TRUNCATE";
/// <summary>
/// Gets the page size.
/// </summary>
/// <value>The page size or null.</value>
protected virtual int? PageSize => null;
/// <summary>
/// Gets the temp store mode.
/// </summary>
/// <value>The temp store mode.</value>
/// <see cref="TempStoreMode"/>
protected virtual TempStoreMode TempStore => TempStoreMode.Default;
/// <summary>
/// Gets the synchronous mode.
/// </summary>
/// <value>The synchronous mode or null.</value>
/// <see cref="SynchronousMode"/>
protected virtual SynchronousMode? Synchronous => null;
/// <summary>
/// Gets or sets the write lock.
/// </summary>
/// <value>The write lock.</value>
protected SemaphoreSlim WriteLock { get; set; } = new SemaphoreSlim(1, 1);
/// <summary>
/// Gets or sets the write connection.
/// </summary>
/// <value>The write connection.</value>
protected SQLiteDatabaseConnection WriteConnection { get; set; }
protected ManagedConnection GetConnection(bool _ = false)
{
SQLite3.EnableSharedCache = false;
int rc = raw.sqlite3_config(raw.SQLITE_CONFIG_MEMSTATUS, 0);
//CheckOk(rc);
rc = raw.sqlite3_config(raw.SQLITE_CONFIG_MULTITHREAD, 1);
//rc = raw.sqlite3_config(raw.SQLITE_CONFIG_SINGLETHREAD, 1);
//rc = raw.sqlite3_config(raw.SQLITE_CONFIG_SERIALIZED, 1);
//CheckOk(rc);
rc = raw.sqlite3_enable_shared_cache(1);
ThreadSafeMode = raw.sqlite3_threadsafe();
}
private static bool _versionLogged;
private string _defaultWal;
protected ManagedConnection _connection;
protected virtual bool EnableSingleConnection => true;
protected ManagedConnection CreateConnection(bool isReadOnly = false)
{
if (_connection != null)
WriteLock.Wait();
if (WriteConnection != null)
{
return _connection;
return new ManagedConnection(WriteConnection, WriteLock);
}
lock (WriteLock)
WriteConnection = SQLite3.Open(
DbFilePath,
DefaultConnectionFlags | ConnectionFlags.Create | ConnectionFlags.ReadWrite,
null);
if (CacheSize.HasValue)
{
if (!_versionLogged)
{
_versionLogged = true;
Logger.LogInformation("Sqlite version: " + SQLite3.Version);
Logger.LogInformation("Sqlite compiler options: " + string.Join(",", SQLite3.CompilerOptions.ToArray()));
}
ConnectionFlags connectionFlags;
if (isReadOnly)
{
//Logger.LogInformation("Opening read connection");
//connectionFlags = ConnectionFlags.ReadOnly;
connectionFlags = ConnectionFlags.Create;
connectionFlags |= ConnectionFlags.ReadWrite;
}
else
{
//Logger.LogInformation("Opening write connection");
connectionFlags = ConnectionFlags.Create;
connectionFlags |= ConnectionFlags.ReadWrite;
}
if (EnableSingleConnection)
{
connectionFlags |= ConnectionFlags.PrivateCache;
}
else
{
connectionFlags |= ConnectionFlags.SharedCached;
}
connectionFlags |= ConnectionFlags.NoMutex;
var db = SQLite3.Open(DbFilePath, connectionFlags, null);
try
{
if (string.IsNullOrWhiteSpace(_defaultWal))
{
_defaultWal = db.Query("PRAGMA journal_mode").SelectScalarString().First();
Logger.LogInformation("Default journal_mode for {0} is {1}", DbFilePath, _defaultWal);
}
var queries = new List<string>
{
//"PRAGMA cache size=-10000"
//"PRAGMA read_uncommitted = true",
"PRAGMA synchronous=Normal"
};
if (CacheSize.HasValue)
{
queries.Add("PRAGMA cache_size=" + CacheSize.Value.ToString(CultureInfo.InvariantCulture));
}
if (EnableTempStoreMemory)
{
queries.Add("PRAGMA temp_store = memory");
}
else
{
queries.Add("PRAGMA temp_store = file");
}
foreach (var query in queries)
{
db.Execute(query);
}
}
catch
{
using (db)
{
}
throw;
}
_connection = new ManagedConnection(db, false);
return _connection;
WriteConnection.Execute("PRAGMA cache_size=" + CacheSize.Value);
}
if (!string.IsNullOrWhiteSpace(JournalMode))
{
WriteConnection.Execute("PRAGMA journal_mode=" + JournalMode);
}
if (Synchronous.HasValue)
{
WriteConnection.Execute("PRAGMA synchronous=" + (int)Synchronous.Value);
}
if (PageSize.HasValue)
{
WriteConnection.Execute("PRAGMA page_size=" + PageSize.Value);
}
WriteConnection.Execute("PRAGMA temp_store=" + (int)TempStore);
// Configuration and pragmas can affect VACUUM so it needs to be last.
WriteConnection.Execute("VACUUM");
return new ManagedConnection(WriteConnection, WriteLock);
}
public IStatement PrepareStatement(ManagedConnection connection, string sql)
{
return connection.PrepareStatement(sql);
}
public IStatement PrepareStatementSafe(ManagedConnection connection, string sql)
{
return connection.PrepareStatement(sql);
}
=> connection.PrepareStatement(sql);
public IStatement PrepareStatement(IDatabaseConnection connection, string sql)
{
return connection.PrepareStatement(sql);
}
=> connection.PrepareStatement(sql);
public IStatement PrepareStatementSafe(IDatabaseConnection connection, string sql)
{
return connection.PrepareStatement(sql);
}
public List<IStatement> PrepareAll(IDatabaseConnection connection, IEnumerable<string> sql)
{
return PrepareAllSafe(connection, sql);
}
public List<IStatement> PrepareAllSafe(IDatabaseConnection connection, IEnumerable<string> sql)
{
return sql.Select(connection.PrepareStatement).ToList();
}
public IEnumerable<IStatement> PrepareAll(IDatabaseConnection connection, IEnumerable<string> sql)
=> sql.Select(connection.PrepareStatement);
protected bool TableExists(ManagedConnection connection, string name)
{
@@ -199,103 +160,9 @@ namespace Emby.Server.Implementations.Data
}, ReadTransactionMode);
}
protected void RunDefaultInitialization(ManagedConnection db)
{
var queries = new List<string>
{
"PRAGMA journal_mode=WAL",
"PRAGMA page_size=4096",
"PRAGMA synchronous=Normal"
};
if (EnableTempStoreMemory)
{
queries.AddRange(new List<string>
{
"pragma default_temp_store = memory",
"pragma temp_store = memory"
});
}
else
{
queries.AddRange(new List<string>
{
"pragma temp_store = file"
});
}
db.ExecuteAll(string.Join(";", queries));
Logger.LogInformation("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First());
}
protected virtual bool EnableTempStoreMemory => false;
protected virtual int? CacheSize => null;
private bool _disposed;
protected void CheckDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().Name, "Object has been disposed and cannot be accessed.");
}
}
public void Dispose()
{
_disposed = true;
Dispose(true);
}
private readonly object _disposeLock = new object();
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
if (dispose)
{
DisposeConnection();
}
}
private void DisposeConnection()
{
try
{
lock (_disposeLock)
{
using (WriteLock.Write())
{
if (_connection != null)
{
using (_connection)
{
_connection.Close();
}
_connection = null;
}
CloseConnection();
}
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Error disposing database");
}
}
protected virtual void CloseConnection()
{
}
protected List<string> GetColumnNames(IDatabaseConnection connection, string table)
{
var list = new List<string>();
var columnNames = new List<string>();
foreach (var row in connection.Query("PRAGMA table_info(" + table + ")"))
{
@@ -303,11 +170,11 @@ namespace Emby.Server.Implementations.Data
{
var name = row[1].ToString();
list.Add(name);
columnNames.Add(name);
}
}
return list;
return columnNames;
}
protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type, List<string> existingColumnNames)
@@ -319,61 +186,103 @@ namespace Emby.Server.Implementations.Data
connection.Execute("alter table " + table + " add column " + columnName + " " + type + " NULL");
}
protected void CheckDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().Name, "Object has been disposed and cannot be accessed.");
}
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
if (_disposed)
{
return;
}
if (dispose)
{
WriteLock.Wait();
try
{
WriteConnection?.Dispose();
}
finally
{
WriteLock.Release();
}
WriteLock.Dispose();
}
WriteConnection = null;
WriteLock = null;
_disposed = true;
}
}
public static class ReaderWriterLockSlimExtensions
/// <summary>
/// The disk synchronization mode, controls how aggressively SQLite will write data
/// all the way out to physical storage.
/// </summary>
public enum SynchronousMode
{
private sealed class ReadLockToken : IDisposable
{
private ReaderWriterLockSlim _sync;
public ReadLockToken(ReaderWriterLockSlim sync)
{
_sync = sync;
sync.EnterReadLock();
}
public void Dispose()
{
if (_sync != null)
{
_sync.ExitReadLock();
_sync = null;
}
}
}
private sealed class WriteLockToken : IDisposable
{
private ReaderWriterLockSlim _sync;
public WriteLockToken(ReaderWriterLockSlim sync)
{
_sync = sync;
sync.EnterWriteLock();
}
public void Dispose()
{
if (_sync != null)
{
_sync.ExitWriteLock();
_sync = null;
}
}
}
/// <summary>
/// SQLite continues without syncing as soon as it has handed data off to the operating system
/// </summary>
Off = 0,
public static IDisposable Read(this ReaderWriterLockSlim obj)
{
//if (BaseSqliteRepository.ThreadSafeMode > 0)
//{
// return new DummyToken();
//}
return new WriteLockToken(obj);
}
/// <summary>
/// SQLite database engine will still sync at the most critical moments
/// </summary>
Normal = 1,
public static IDisposable Write(this ReaderWriterLockSlim obj)
{
//if (BaseSqliteRepository.ThreadSafeMode > 0)
//{
// return new DummyToken();
//}
return new WriteLockToken(obj);
}
/// <summary>
/// SQLite database engine will use the xSync method of the VFS
/// to ensure that all content is safely written to the disk surface prior to continuing.
/// </summary>
Full = 2,
/// <summary>
/// EXTRA synchronous is like FULL with the addition that the directory containing a rollback journal
/// is synced after that journal is unlinked to commit a transaction in DELETE mode.
/// </summary>
Extra = 3
}
/// <summary>
/// Storage mode used by temporary database files.
/// </summary>
public enum TempStoreMode
{
/// <summary>
/// The compile-time C preprocessor macro SQLITE_TEMP_STORE
/// is used to determine where temporary tables and indices are stored.
/// </summary>
Default = 0,
/// <summary>
/// Temporary tables and indices are stored in a file.
/// </summary>
File = 1,
/// <summary>
/// Temporary tables and indices are kept in as if they were pure in-memory databases memory.
/// </summary>
Memory = 2
}
}

View File

@@ -1,79 +1,78 @@
using System;
using System.Collections.Generic;
using System.Threading;
using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Data
{
public class ManagedConnection : IDisposable
{
private SQLiteDatabaseConnection db;
private readonly bool _closeOnDispose;
private SQLiteDatabaseConnection _db;
private readonly SemaphoreSlim _writeLock;
private bool _disposed = false;
public ManagedConnection(SQLiteDatabaseConnection db, bool closeOnDispose)
public ManagedConnection(SQLiteDatabaseConnection db, SemaphoreSlim writeLock)
{
this.db = db;
_closeOnDispose = closeOnDispose;
_db = db;
_writeLock = writeLock;
}
public IStatement PrepareStatement(string sql)
{
return db.PrepareStatement(sql);
return _db.PrepareStatement(sql);
}
public IEnumerable<IStatement> PrepareAll(string sql)
{
return db.PrepareAll(sql);
return _db.PrepareAll(sql);
}
public void ExecuteAll(string sql)
{
db.ExecuteAll(sql);
_db.ExecuteAll(sql);
}
public void Execute(string sql, params object[] values)
{
db.Execute(sql, values);
_db.Execute(sql, values);
}
public void RunQueries(string[] sql)
{
db.RunQueries(sql);
_db.RunQueries(sql);
}
public void RunInTransaction(Action<IDatabaseConnection> action, TransactionMode mode)
{
db.RunInTransaction(action, mode);
_db.RunInTransaction(action, mode);
}
public T RunInTransaction<T>(Func<IDatabaseConnection, T> action, TransactionMode mode)
{
return db.RunInTransaction(action, mode);
return _db.RunInTransaction(action, mode);
}
public IEnumerable<IReadOnlyList<IResultSetValue>> Query(string sql)
{
return db.Query(sql);
return _db.Query(sql);
}
public IEnumerable<IReadOnlyList<IResultSetValue>> Query(string sql, params object[] values)
{
return db.Query(sql, values);
}
public void Close()
{
using (db)
{
}
return _db.Query(sql, values);
}
public void Dispose()
{
if (_closeOnDispose)
if (_disposed)
{
Close();
return;
}
_writeLock.Release();
_db = null; // Don't dispose it
_disposed = true;
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
using MediaBrowser.Common.Configuration;
@@ -18,13 +19,13 @@ namespace Emby.Server.Implementations.Data
/// </summary>
public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository
{
protected IFileSystem FileSystem { get; private set; }
private readonly IFileSystem _fileSystem;
public SqliteDisplayPreferencesRepository(ILoggerFactory loggerFactory, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, IFileSystem fileSystem)
: base(loggerFactory.CreateLogger(nameof(SqliteDisplayPreferencesRepository)))
public SqliteDisplayPreferencesRepository(ILogger<SqliteDisplayPreferencesRepository> logger, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, IFileSystem fileSystem)
: base(logger)
{
_jsonSerializer = jsonSerializer;
FileSystem = fileSystem;
_fileSystem = fileSystem;
DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db");
}
@@ -49,7 +50,7 @@ namespace Emby.Server.Implementations.Data
{
Logger.LogError(ex, "Error loading database file. Will reset and retry.");
FileSystem.DeleteFile(DbFilePath);
_fileSystem.DeleteFile(DbFilePath);
InitializeInternal();
}
@@ -61,16 +62,14 @@ namespace Emby.Server.Implementations.Data
/// <returns>Task.</returns>
private void InitializeInternal()
{
using (var connection = CreateConnection())
string[] queries =
{
RunDefaultInitialization(connection);
string[] queries = {
"create table if not exists userdisplaypreferences (id GUID NOT NULL, userId GUID NOT NULL, client text NOT NULL, data BLOB NOT NULL)",
"create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)"
};
"create table if not exists userdisplaypreferences (id GUID NOT NULL, userId GUID NOT NULL, client text NOT NULL, data BLOB NOT NULL)",
"create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)"
};
using (var connection = GetConnection())
{
connection.RunQueries(queries);
}
}
@@ -82,7 +81,6 @@ namespace Emby.Server.Implementations.Data
/// <param name="userId">The user id.</param>
/// <param name="client">The client.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
/// <exception cref="ArgumentNullException">item</exception>
public void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, CancellationToken cancellationToken)
{
@@ -98,15 +96,11 @@ namespace Emby.Server.Implementations.Data
cancellationToken.ThrowIfCancellationRequested();
using (WriteLock.Write())
using (var connection = GetConnection())
{
using (var connection = CreateConnection())
{
connection.RunInTransaction(db =>
{
SaveDisplayPreferences(displayPreferences, userId, client, db);
}, TransactionMode);
}
connection.RunInTransaction(
db => SaveDisplayPreferences(displayPreferences, userId, client, db),
TransactionMode);
}
}
@@ -131,7 +125,6 @@ namespace Emby.Server.Implementations.Data
/// <param name="displayPreferences">The display preferences.</param>
/// <param name="userId">The user id.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
/// <exception cref="ArgumentNullException">item</exception>
public void SaveAllDisplayPreferences(IEnumerable<DisplayPreferences> displayPreferences, Guid userId, CancellationToken cancellationToken)
{
@@ -142,18 +135,17 @@ namespace Emby.Server.Implementations.Data
cancellationToken.ThrowIfCancellationRequested();
using (WriteLock.Write())
using (var connection = GetConnection())
{
using (var connection = CreateConnection())
{
connection.RunInTransaction(db =>
connection.RunInTransaction(
db =>
{
foreach (var displayPreference in displayPreferences)
{
SaveDisplayPreferences(displayPreference, userId, displayPreference.Client, db);
}
}, TransactionMode);
}
},
TransactionMode);
}
}
@@ -174,28 +166,25 @@ namespace Emby.Server.Implementations.Data
var guidId = displayPreferencesId.GetMD5();
using (WriteLock.Read())
using (var connection = GetConnection(true))
{
using (var connection = CreateConnection(true))
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client"))
{
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client"))
{
statement.TryBind("@id", guidId.ToGuidBlob());
statement.TryBind("@userId", userId.ToGuidBlob());
statement.TryBind("@client", client);
statement.TryBind("@id", guidId.ToGuidBlob());
statement.TryBind("@userId", userId.ToGuidBlob());
statement.TryBind("@client", client);
foreach (var row in statement.ExecuteQuery())
{
return Get(row);
}
foreach (var row in statement.ExecuteQuery())
{
return Get(row);
}
return new DisplayPreferences
{
Id = guidId.ToString("N")
};
}
}
return new DisplayPreferences
{
Id = guidId.ToString("N", CultureInfo.InvariantCulture)
};
}
/// <summary>
@@ -208,18 +197,15 @@ namespace Emby.Server.Implementations.Data
{
var list = new List<DisplayPreferences>();
using (WriteLock.Read())
using (var connection = GetConnection(true))
{
using (var connection = CreateConnection(true))
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId"))
{
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId"))
{
statement.TryBind("@userId", userId.ToGuidBlob());
statement.TryBind("@userId", userId.ToGuidBlob());
foreach (var row in statement.ExecuteQuery())
{
list.Add(Get(row));
}
foreach (var row in statement.ExecuteQuery())
{
list.Add(Get(row));
}
}
}
@@ -228,22 +214,12 @@ namespace Emby.Server.Implementations.Data
}
private DisplayPreferences Get(IReadOnlyList<IResultSetValue> row)
{
using (var stream = new MemoryStream(row[0].ToBlob()))
{
stream.Position = 0;
return _jsonSerializer.DeserializeFromStream<DisplayPreferences>(stream);
}
}
=> _jsonSerializer.DeserializeFromString<DisplayPreferences>(row.GetString(0));
public void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken)
{
SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken);
}
=> SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken);
public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, string userId, string client)
{
return GetDisplayPreferences(displayPreferencesId, new Guid(userId), client);
}
=> GetDisplayPreferences(displayPreferencesId, new Guid(userId), client);
}
}

View File

@@ -18,10 +18,6 @@ namespace Emby.Server.Implementations.Data
connection.RunInTransaction(conn =>
{
//foreach (var query in queries)
//{
// conn.Execute(query);
//}
conn.ExecuteAll(string.Join(";", queries));
});
}
@@ -38,7 +34,8 @@ namespace Emby.Server.Implementations.Data
public static Guid ReadGuidFromBlob(this IResultSetValue result)
{
return new Guid(result.ToBlob());
// TODO: Remove ToArray when upgrading to netstandard2.1
return new Guid(result.ToBlob().ToArray());
}
public static string ToDateTimeParamValue(this DateTime dateValue)
@@ -141,7 +138,7 @@ namespace Emby.Server.Implementations.Data
}
}
public static void Attach(ManagedConnection db, string path, string alias)
public static void Attach(SQLiteDatabaseConnection db, string path, string alias)
{
var commandText = string.Format("attach @path as {0};", alias);

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
@@ -33,19 +32,19 @@ namespace Emby.Server.Implementations.Data
/// Opens the connection to the database
/// </summary>
/// <returns>Task.</returns>
public void Initialize(ReaderWriterLockSlim writeLock, ManagedConnection managedConnection, IUserManager userManager)
public void Initialize(IUserManager userManager, SemaphoreSlim dbLock, SQLiteDatabaseConnection dbConnection)
{
_connection = managedConnection;
WriteLock.Dispose();
WriteLock = writeLock;
WriteLock = dbLock;
WriteConnection?.Dispose();
WriteConnection = dbConnection;
using (var connection = CreateConnection())
using (var connection = GetConnection())
{
var userDatasTableExists = TableExists(connection, "UserDatas");
var userDataTableExists = TableExists(connection, "userdata");
var users = userDatasTableExists ? null : userManager.Users.ToArray();
var users = userDatasTableExists ? null : userManager.Users;
connection.RunInTransaction(db =>
{
@@ -85,7 +84,7 @@ namespace Emby.Server.Implementations.Data
}
}
private void ImportUserIds(IDatabaseConnection db, User[] users)
private void ImportUserIds(IDatabaseConnection db, IEnumerable<User> users)
{
var userIdsWithUserData = GetAllUserIdsWithUserData(db);
@@ -129,8 +128,6 @@ namespace Emby.Server.Implementations.Data
return list;
}
protected override bool EnableTempStoreMemory => true;
/// <summary>
/// Saves the user data.
/// </summary>
@@ -178,15 +175,12 @@ namespace Emby.Server.Implementations.Data
{
cancellationToken.ThrowIfCancellationRequested();
using (WriteLock.Write())
using (var connection = GetConnection())
{
using (var connection = CreateConnection())
connection.RunInTransaction(db =>
{
connection.RunInTransaction(db =>
{
SaveUserData(db, internalUserId, key, userData);
}, TransactionMode);
}
SaveUserData(db, internalUserId, key, userData);
}, TransactionMode);
}
}
@@ -249,18 +243,15 @@ namespace Emby.Server.Implementations.Data
{
cancellationToken.ThrowIfCancellationRequested();
using (WriteLock.Write())
using (var connection = GetConnection())
{
using (var connection = CreateConnection())
connection.RunInTransaction(db =>
{
connection.RunInTransaction(db =>
foreach (var userItemData in userDataList)
{
foreach (var userItemData in userDataList)
{
SaveUserData(db, internalUserId, userItemData.Key, userItemData);
}
}, TransactionMode);
}
SaveUserData(db, internalUserId, userItemData.Key, userItemData);
}
}, TransactionMode);
}
}
@@ -281,28 +272,26 @@ namespace Emby.Server.Implementations.Data
{
throw new ArgumentNullException(nameof(internalUserId));
}
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException(nameof(key));
}
using (WriteLock.Read())
using (var connection = GetConnection(true))
{
using (var connection = CreateConnection(true))
using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId"))
{
using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId"))
statement.TryBind("@UserId", internalUserId);
statement.TryBind("@Key", key);
foreach (var row in statement.ExecuteQuery())
{
statement.TryBind("@UserId", internalUserId);
statement.TryBind("@Key", key);
foreach (var row in statement.ExecuteQuery())
{
return ReadRow(row);
}
return ReadRow(row);
}
return null;
}
return null;
}
}
@@ -335,18 +324,15 @@ namespace Emby.Server.Implementations.Data
var list = new List<UserItemData>();
using (WriteLock.Read())
using (var connection = GetConnection())
{
using (var connection = CreateConnection())
using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where userId=@UserId"))
{
using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where userId=@UserId"))
{
statement.TryBind("@UserId", internalUserId);
statement.TryBind("@UserId", internalUserId);
foreach (var row in statement.ExecuteQuery())
{
list.Add(ReadRow(row));
}
foreach (var row in statement.ExecuteQuery())
{
list.Add(ReadRow(row));
}
}
}
@@ -392,15 +378,5 @@ namespace Emby.Server.Implementations.Data
return userData;
}
protected override void Dispose(bool dispose)
{
// handled by library database
}
protected override void CloseConnection()
{
// handled by library database
}
}
}

View File

@@ -18,10 +18,10 @@ namespace Emby.Server.Implementations.Data
private readonly IJsonSerializer _jsonSerializer;
public SqliteUserRepository(
ILoggerFactory loggerFactory,
ILogger<SqliteUserRepository> logger,
IServerApplicationPaths appPaths,
IJsonSerializer jsonSerializer)
: base(loggerFactory.CreateLogger(nameof(SqliteUserRepository)))
: base(logger)
{
_jsonSerializer = jsonSerializer;
@@ -35,15 +35,12 @@ namespace Emby.Server.Implementations.Data
public string Name => "SQLite";
/// <summary>
/// Opens the connection to the database
/// Opens the connection to the database.
/// </summary>
/// <returns>Task.</returns>
public void Initialize()
{
using (var connection = CreateConnection())
using (var connection = GetConnection())
{
RunDefaultInitialization(connection);
var localUsersTableExists = TableExists(connection, "LocalUsersv2");
connection.RunQueries(new[] {
@@ -56,7 +53,7 @@ namespace Emby.Server.Implementations.Data
TryMigrateToLocalUsersTable(connection);
}
RemoveEmptyPasswordHashes();
RemoveEmptyPasswordHashes(connection);
}
}
@@ -75,13 +72,13 @@ namespace Emby.Server.Implementations.Data
}
}
private void RemoveEmptyPasswordHashes()
private void RemoveEmptyPasswordHashes(ManagedConnection connection)
{
foreach (var user in RetrieveAllUsers())
foreach (var user in RetrieveAllUsers(connection))
{
// If the user password is the sha1 hash of the empty string, remove it
if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)
|| !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal))
&& !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal))
{
continue;
}
@@ -89,22 +86,16 @@ namespace Emby.Server.Implementations.Data
user.Password = null;
var serialized = _jsonSerializer.SerializeToBytes(user);
using (WriteLock.Write())
using (var connection = CreateConnection())
connection.RunInTransaction(db =>
{
connection.RunInTransaction(db =>
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
{
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
{
statement.TryBind("@InternalId", user.InternalId);
statement.TryBind("@data", serialized);
statement.MoveNext();
}
}, TransactionMode);
}
statement.TryBind("@InternalId", user.InternalId);
statement.TryBind("@data", serialized);
statement.MoveNext();
}
}, TransactionMode);
}
}
/// <summary>
@@ -119,31 +110,28 @@ namespace Emby.Server.Implementations.Data
var serialized = _jsonSerializer.SerializeToBytes(user);
using (WriteLock.Write())
using (var connection = GetConnection())
{
using (var connection = CreateConnection())
connection.RunInTransaction(db =>
{
connection.RunInTransaction(db =>
using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)"))
{
using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)"))
{
statement.TryBind("@guid", user.Id.ToGuidBlob());
statement.TryBind("@data", serialized);
statement.TryBind("@guid", user.Id.ToGuidBlob());
statement.TryBind("@data", serialized);
statement.MoveNext();
}
statement.MoveNext();
}
var createdUser = GetUser(user.Id, false);
var createdUser = GetUser(user.Id, connection);
if (createdUser == null)
{
throw new ApplicationException("created user should never be null");
}
if (createdUser == null)
{
throw new ApplicationException("created user should never be null");
}
user.InternalId = createdUser.InternalId;
user.InternalId = createdUser.InternalId;
}, TransactionMode);
}
}, TransactionMode);
}
}
@@ -156,39 +144,30 @@ namespace Emby.Server.Implementations.Data
var serialized = _jsonSerializer.SerializeToBytes(user);
using (WriteLock.Write())
using (var connection = GetConnection())
{
using (var connection = CreateConnection())
connection.RunInTransaction(db =>
{
connection.RunInTransaction(db =>
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
{
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
{
statement.TryBind("@InternalId", user.InternalId);
statement.TryBind("@data", serialized);
statement.MoveNext();
}
statement.TryBind("@InternalId", user.InternalId);
statement.TryBind("@data", serialized);
statement.MoveNext();
}
}, TransactionMode);
}
}, TransactionMode);
}
}
private User GetUser(Guid guid, bool openLock)
private User GetUser(Guid guid, ManagedConnection connection)
{
using (openLock ? WriteLock.Read() : null)
using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid"))
{
using (var connection = CreateConnection(true))
{
using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid"))
{
statement.TryBind("@guid", guid);
statement.TryBind("@guid", guid);
foreach (var row in statement.ExecuteQuery())
{
return GetUser(row);
}
}
foreach (var row in statement.ExecuteQuery())
{
return GetUser(row);
}
}
@@ -200,14 +179,10 @@ namespace Emby.Server.Implementations.Data
var id = row[0].ToInt64();
var guid = row[1].ReadGuidFromBlob();
using (var stream = new MemoryStream(row[2].ToBlob()))
{
stream.Position = 0;
var user = _jsonSerializer.DeserializeFromStream<User>(stream);
user.InternalId = id;
user.Id = guid;
return user;
}
var user = _jsonSerializer.DeserializeFromString<User>(row.GetString(2));
user.InternalId = id;
user.Id = guid;
return user;
}
/// <summary>
@@ -216,20 +191,22 @@ namespace Emby.Server.Implementations.Data
/// <returns>IEnumerable{User}.</returns>
public List<User> RetrieveAllUsers()
{
var list = new List<User>();
using (WriteLock.Read())
using (var connection = GetConnection(true))
{
using (var connection = CreateConnection(true))
{
foreach (var row in connection.Query("select id,guid,data from LocalUsersv2"))
{
list.Add(GetUser(row));
}
}
return new List<User>(RetrieveAllUsers(connection));
}
}
return list;
/// <summary>
/// Retrieve all users from the database
/// </summary>
/// <returns>IEnumerable{User}.</returns>
private IEnumerable<User> RetrieveAllUsers(ManagedConnection connection)
{
foreach (var row in connection.Query("select id,guid,data from LocalUsersv2"))
{
yield return GetUser(row);
}
}
/// <summary>
@@ -245,19 +222,16 @@ namespace Emby.Server.Implementations.Data
throw new ArgumentNullException(nameof(user));
}
using (WriteLock.Write())
using (var connection = GetConnection())
{
using (var connection = CreateConnection())
connection.RunInTransaction(db =>
{
connection.RunInTransaction(db =>
using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id"))
{
using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id"))
{
statement.TryBind("@id", user.InternalId);
statement.MoveNext();
}
}, TransactionMode);
}
statement.TryBind("@id", user.InternalId);
statement.MoveNext();
}
}, TransactionMode);
}
}
}

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