mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-12-12 03:53:01 +03:00
Compare commits
590 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46c37c0ae8 | ||
|
|
4ad71766fc | ||
|
|
9d60cc8c66 | ||
|
|
4a6243096a | ||
|
|
10cbdc8e8e | ||
|
|
4886fc467c | ||
|
|
8e2827cc39 | ||
|
|
395d2e4917 | ||
|
|
d31f5229da | ||
|
|
d6622818dc | ||
|
|
ba684d6d3a | ||
|
|
90c04a4640 | ||
|
|
06a1e1f166 | ||
|
|
31ad366aa9 | ||
|
|
10f33b0273 | ||
|
|
eaa1ac8013 | ||
|
|
ca3bb308b3 | ||
|
|
e790f024c2 | ||
|
|
250e0c75df | ||
|
|
cde7375049 | ||
|
|
c7fedfbca3 | ||
|
|
f520831025 | ||
|
|
2f0719a883 | ||
|
|
34ab99caf1 | ||
|
|
b2f94c0e40 | ||
|
|
65bff1181a | ||
|
|
efb14f0b58 | ||
|
|
75a4f04cce | ||
|
|
007fe34363 | ||
|
|
a7e31ef31f | ||
|
|
eae0c28e6d | ||
|
|
21950382b9 | ||
|
|
1af9c047fb | ||
|
|
2d19bfa7fb | ||
|
|
f5f7de64de | ||
|
|
754e76a61b | ||
|
|
09505e0988 | ||
|
|
67e206fa0f | ||
|
|
608fd873de | ||
|
|
05a4161fd3 | ||
|
|
05040351dc | ||
|
|
d75324afc9 | ||
|
|
38fcd31917 | ||
|
|
816d8a0216 | ||
|
|
e37ccd6ec0 | ||
|
|
f27477da26 | ||
|
|
c032a015a4 | ||
|
|
11e81b035a | ||
|
|
891a03c038 | ||
|
|
31aa6c486c | ||
|
|
1d9133a5e8 | ||
|
|
3375ca5a8c | ||
|
|
1596e93cc1 | ||
|
|
1a540f1cf7 | ||
|
|
05f5cd1bde | ||
|
|
f0fbd0232c | ||
|
|
72dd609109 | ||
|
|
13e94a8b1b | ||
|
|
d9e7883fb5 | ||
|
|
2d396cb589 | ||
|
|
b56031b9f3 | ||
|
|
fd86b141e2 | ||
|
|
427a3e9b08 | ||
|
|
3001f21f8d | ||
|
|
48b50a22a4 | ||
|
|
5e8496bc59 | ||
|
|
2dbc1153e8 | ||
|
|
b07c146fd9 | ||
|
|
cc2edc4d66 | ||
|
|
524357bfa8 | ||
|
|
7343e07fe5 | ||
|
|
6be8624373 | ||
|
|
740c95d557 | ||
|
|
31607fbb37 | ||
|
|
122cba2aa7 | ||
|
|
b44a70ff36 | ||
|
|
1b03f078b9 | ||
|
|
4c8f8cf64c | ||
|
|
d623f616fa | ||
|
|
fc8de8aead | ||
|
|
6480cfcc87 | ||
|
|
e36d424b5f | ||
|
|
f7e7d72688 | ||
|
|
3474568ce2 | ||
|
|
89f2dfd78a | ||
|
|
2c4c56d6d6 | ||
|
|
087d4153ae | ||
|
|
86772bd7bd | ||
|
|
4e2841f0d7 | ||
|
|
26fe4040bf | ||
|
|
fb7f29de18 | ||
|
|
d18252542d | ||
|
|
030fcaac15 | ||
|
|
7ebb043249 | ||
|
|
598b1c9966 | ||
|
|
cf36aaef2b | ||
|
|
f30af9cd5f | ||
|
|
5024c52c60 | ||
|
|
e971b62dab | ||
|
|
4ffec8ad26 | ||
|
|
69cc5814d8 | ||
|
|
414a318a0d | ||
|
|
758e35baba | ||
|
|
09921a00aa | ||
|
|
16a3bb2df4 | ||
|
|
2c684c0231 | ||
|
|
949a1cce21 | ||
|
|
0e787f4e9f | ||
|
|
bd31091648 | ||
|
|
4cd8903abc | ||
|
|
7b01de8db1 | ||
|
|
f73d8a44df | ||
|
|
752d65d020 | ||
|
|
c2667f99f4 | ||
|
|
c7e7aa0a61 | ||
|
|
fc79659549 | ||
|
|
e81a6adb95 | ||
|
|
80aedcd7e2 | ||
|
|
fc28c9237c | ||
|
|
b04200ca68 | ||
|
|
67fbbcfd12 | ||
|
|
e3dbed1c1a | ||
|
|
480a6607e2 | ||
|
|
4a30fee40d | ||
|
|
7f0fa74467 | ||
|
|
3d1d27230d | ||
|
|
9df1506794 | ||
|
|
2d0844b5db | ||
|
|
1ee016c997 | ||
|
|
221389089c | ||
|
|
59031ee3b8 | ||
|
|
d2e408539e | ||
|
|
a8140cc74b | ||
|
|
d5f080fefb | ||
|
|
35ff8ec713 | ||
|
|
764c901cd7 | ||
|
|
f85d45d17f | ||
|
|
022bd1b8b4 | ||
|
|
31305af7ff | ||
|
|
dd929d796f | ||
|
|
11fde02035 | ||
|
|
3d85014edc | ||
|
|
d125fbc43d | ||
|
|
427688a0a0 | ||
|
|
21cc38fcf4 | ||
|
|
ee7bf86e0f | ||
|
|
f8bb7a7ff4 | ||
|
|
6d3e6d800f | ||
|
|
208585d3f6 | ||
|
|
bf00dedc7f | ||
|
|
bf43dc00bb | ||
|
|
e64aaebbac | ||
|
|
1d443d2ff5 | ||
|
|
6507a9b2ee | ||
|
|
4cc4d57a78 | ||
|
|
605bf0e8c3 | ||
|
|
89e2af6b57 | ||
|
|
b864e9da2a | ||
|
|
f10382a696 | ||
|
|
297f25cfc2 | ||
|
|
afdef163ea | ||
|
|
6751560228 | ||
|
|
2012eb5e11 | ||
|
|
58068e249a | ||
|
|
3ddbda9aca | ||
|
|
1fef8bf266 | ||
|
|
3c4043199a | ||
|
|
0220309ea7 | ||
|
|
497b4f834f | ||
|
|
715ddbb3b0 | ||
|
|
0ff038f0a2 | ||
|
|
7322485a6d | ||
|
|
f77af5f6e4 | ||
|
|
2324c408ba | ||
|
|
037cf9e1ee | ||
|
|
5268553e7f | ||
|
|
5f7524aca2 | ||
|
|
86f5221f96 | ||
|
|
ecf85a73ec | ||
|
|
132ce3ece1 | ||
|
|
25deca9579 | ||
|
|
93d15cd969 | ||
|
|
4b91c9bf66 | ||
|
|
16adaa64c9 | ||
|
|
93fe7957fb | ||
|
|
fb96763f65 | ||
|
|
63342f89d4 | ||
|
|
47095e6cf8 | ||
|
|
f2062ba19b | ||
|
|
58061a7295 | ||
|
|
c6c398179a | ||
|
|
e498e47109 | ||
|
|
40089e2fd9 | ||
|
|
045a0419f6 | ||
|
|
80fb3dc2cb | ||
|
|
fcee64df09 | ||
|
|
757e0194b9 | ||
|
|
4625592a83 | ||
|
|
e3b844b5aa | ||
|
|
decaffed86 | ||
|
|
669c48cc8b | ||
|
|
d1fe24ac92 | ||
|
|
3fa43a1e08 | ||
|
|
028a98d2c1 | ||
|
|
0f70a81db3 | ||
|
|
d014a1dc1d | ||
|
|
e04a152ed0 | ||
|
|
8a680ae324 | ||
|
|
75996476a7 | ||
|
|
620d7b560d | ||
|
|
ab9859ecef | ||
|
|
37ea50a572 | ||
|
|
5cb3f04389 | ||
|
|
cedf50eeec | ||
|
|
c5fce647de | ||
|
|
10a0d6bdba | ||
|
|
0abe57e930 | ||
|
|
65c0b486aa | ||
|
|
ae0ecc1b10 | ||
|
|
dfff68b2f4 | ||
|
|
e4c5d51860 | ||
|
|
8c609bc9ce | ||
|
|
f486f5966f | ||
|
|
e91dd14b31 | ||
|
|
8a53b60912 | ||
|
|
a4b52b7264 | ||
|
|
5ae0ef0527 | ||
|
|
dfb1d704ed | ||
|
|
ffd6dac03a | ||
|
|
8f4895e8a5 | ||
|
|
a9302b8b53 | ||
|
|
c31b0b311b | ||
|
|
bba049c987 | ||
|
|
21c2acc520 | ||
|
|
394d23a73a | ||
|
|
276428878e | ||
|
|
04db0369d4 | ||
|
|
4ef7eda593 | ||
|
|
2242c8d793 | ||
|
|
bef665be36 | ||
|
|
bf9f342c13 | ||
|
|
a994edda04 | ||
|
|
9f6bca3658 | ||
|
|
2617a49b78 | ||
|
|
656bffbbb2 | ||
|
|
8104e739d5 | ||
|
|
632968dc81 | ||
|
|
ed69e690b8 | ||
|
|
20775116f7 | ||
|
|
446f9bf81f | ||
|
|
913e80fd55 | ||
|
|
9a4a01fb0e | ||
|
|
df92df7bd6 | ||
|
|
78742b8e4c | ||
|
|
942c400c19 | ||
|
|
5587dd8bfb | ||
|
|
8d1fc3f984 | ||
|
|
318e0d4a24 | ||
|
|
bc00617df7 | ||
|
|
2c26517172 | ||
|
|
51648a2a21 | ||
|
|
12df381495 | ||
|
|
17ca23d73b | ||
|
|
41df94115f | ||
|
|
0250204f14 | ||
|
|
0419deeec4 | ||
|
|
9020f68ce1 | ||
|
|
557c4d065d | ||
|
|
6cc1bd544a | ||
|
|
aba22b92bc | ||
|
|
040871459b | ||
|
|
6263b73d9c | ||
|
|
1cc433eabc | ||
|
|
5982cdad90 | ||
|
|
d450169964 | ||
|
|
e823c11b46 | ||
|
|
c328417d29 | ||
|
|
c4192f9f8b | ||
|
|
5368112d90 | ||
|
|
ed07ed44ae | ||
|
|
9993dafe54 | ||
|
|
6bdb5debd2 | ||
|
|
65403747df | ||
|
|
594b271383 | ||
|
|
27f9981142 | ||
|
|
3d3d879b99 | ||
|
|
53ed6e5e6e | ||
|
|
d0e4e0f600 | ||
|
|
311caf3fc3 | ||
|
|
7c13021def | ||
|
|
c8788d83fb | ||
|
|
331260cf80 | ||
|
|
58e5931a32 | ||
|
|
02041fa6f4 | ||
|
|
fcd299965d | ||
|
|
79d5a53aea | ||
|
|
f1086a72bf | ||
|
|
95d001a053 | ||
|
|
b7fd68d366 | ||
|
|
c84729a4f4 | ||
|
|
4951ec9814 | ||
|
|
edba82db37 | ||
|
|
1d1e6dede9 | ||
|
|
f384822aa5 | ||
|
|
1ac282b12e | ||
|
|
588a13377c | ||
|
|
fb1de5a921 | ||
|
|
71ed840944 | ||
|
|
dab8e15052 | ||
|
|
e47d121985 | ||
|
|
c0b95dbc79 | ||
|
|
647adc51c8 | ||
|
|
7668ecf9c9 | ||
|
|
3769453541 | ||
|
|
8c2af50170 | ||
|
|
1e97e4d462 | ||
|
|
27e7e792b3 | ||
|
|
7429c07c05 | ||
|
|
91afaaf8fe | ||
|
|
333bd2107a | ||
|
|
25d3d0b731 | ||
|
|
77addb2283 | ||
|
|
9651a78b0c | ||
|
|
9eba31185a | ||
|
|
aea7edf0fa | ||
|
|
42d4834f63 | ||
|
|
ba78f6a0ff | ||
|
|
feb5b62ad4 | ||
|
|
5ccba7cf8a | ||
|
|
848cfc32cc | ||
|
|
5510e8ebee | ||
|
|
9c02e99e35 | ||
|
|
148db8b81a | ||
|
|
e342b7bc71 | ||
|
|
f1c93ae618 | ||
|
|
4e229ad86b | ||
|
|
e88f079da6 | ||
|
|
4e8de67aca | ||
|
|
a85488cd20 | ||
|
|
5a7cca9d1b | ||
|
|
d6c6f3c10c | ||
|
|
38f52a139e | ||
|
|
194da8416b | ||
|
|
f3e7bc0573 | ||
|
|
c3fa299acc | ||
|
|
852460b991 | ||
|
|
33b67a357f | ||
|
|
4db31acff9 | ||
|
|
968e282c90 | ||
|
|
1731bf7372 | ||
|
|
0804bed66d | ||
|
|
a0606b5730 | ||
|
|
9ba6227db4 | ||
|
|
9bab93262e | ||
|
|
0f9006c81f | ||
|
|
b3438559cc | ||
|
|
8e5cccb22c | ||
|
|
500c0b9cba | ||
|
|
5054a77dcf | ||
|
|
dac2c98d8a | ||
|
|
4df3333b71 | ||
|
|
5262e50fee | ||
|
|
547d0ecf58 | ||
|
|
6e07eab247 | ||
|
|
96b3d37caf | ||
|
|
aafed63c3f | ||
|
|
2e9a3d45c2 | ||
|
|
e281c79d6f | ||
|
|
38ec68c488 | ||
|
|
da61998ad6 | ||
|
|
f28dd79fb1 | ||
|
|
c2e57aba27 | ||
|
|
4a3e42e779 | ||
|
|
67f399dccf | ||
|
|
eb95b025d4 | ||
|
|
de45cfdd8c | ||
|
|
47966793c0 | ||
|
|
0c49079c16 | ||
|
|
d82d6b6aef | ||
|
|
7ae526da8b | ||
|
|
e79d44d9f1 | ||
|
|
fb7de2f966 | ||
|
|
db54fe4c70 | ||
|
|
1eb26bdf08 | ||
|
|
cbd0e71c07 | ||
|
|
2db1826ed8 | ||
|
|
cf4e64f430 | ||
|
|
9b39404b9a | ||
|
|
00fe66a38c | ||
|
|
406af5f086 | ||
|
|
e7e7d96f51 | ||
|
|
785fa76ac6 | ||
|
|
6c967c5982 | ||
|
|
f03e279382 | ||
|
|
1bc2b12ee3 | ||
|
|
469a17b3ca | ||
|
|
affb8c8673 | ||
|
|
74aa38acd7 | ||
|
|
d4ded281aa | ||
|
|
c597f0de35 | ||
|
|
3965f90236 | ||
|
|
8af4417f8f | ||
|
|
4a5c526ccc | ||
|
|
320707d44c | ||
|
|
2690ac299b | ||
|
|
268b099ca8 | ||
|
|
33171a58b5 | ||
|
|
73c1cdb32a | ||
|
|
13bfe5093e | ||
|
|
99bed9a9c3 | ||
|
|
8ef41020d9 | ||
|
|
fca226bdfd | ||
|
|
bca7a26ffd | ||
|
|
60df855b26 | ||
|
|
44ed037e73 | ||
|
|
50ce7572b4 | ||
|
|
74695428fe | ||
|
|
139807719c | ||
|
|
1e2050f106 | ||
|
|
a23f04623e | ||
|
|
1f30a50f4a | ||
|
|
098de6b050 | ||
|
|
a0d31a49a0 | ||
|
|
6bbb968b57 | ||
|
|
6c6e9ca9f2 | ||
|
|
89d4ce309d | ||
|
|
b43317c5e1 | ||
|
|
5917e91447 | ||
|
|
9753a76905 | ||
|
|
24b76dbed8 | ||
|
|
dab25a0eeb | ||
|
|
53beebc774 | ||
|
|
ba003e06ef | ||
|
|
1d631540ac | ||
|
|
3ed9d32f68 | ||
|
|
3ecfd1fdd1 | ||
|
|
24574d4964 | ||
|
|
512ab8c6aa | ||
|
|
029bafdf9c | ||
|
|
0849c4a447 | ||
|
|
5238ba5d8f | ||
|
|
6b1a64652f | ||
|
|
9961c8c459 | ||
|
|
18418b6892 | ||
|
|
d4bc7b5a5a | ||
|
|
e2d0f67077 | ||
|
|
5ef63e738d | ||
|
|
7bbcb455c0 | ||
|
|
2ad54cd09d | ||
|
|
7c9803e135 | ||
|
|
480999e8e6 | ||
|
|
ef17ec700b | ||
|
|
4b34b0cfea | ||
|
|
60fc53306d | ||
|
|
c6188e26af | ||
|
|
18717d103a | ||
|
|
6834b64922 | ||
|
|
56e3063342 | ||
|
|
781cca0c82 | ||
|
|
eab35890dc | ||
|
|
967d5deeb7 | ||
|
|
51ba28bd65 | ||
|
|
a94aeb5c87 | ||
|
|
13f2783a8e | ||
|
|
7554f63551 | ||
|
|
25253cf961 | ||
|
|
77a5617774 | ||
|
|
395072239d | ||
|
|
e2f6ecaef6 | ||
|
|
48e7274d37 | ||
|
|
9f3aa2cead | ||
|
|
bb50363812 | ||
|
|
9a38e4dc8a | ||
|
|
c607c95e64 | ||
|
|
18ae107ce4 | ||
|
|
3f80b16ffa | ||
|
|
72aa364aa5 | ||
|
|
0d5fbcb031 | ||
|
|
a35ea49c99 | ||
|
|
4811e76860 | ||
|
|
64a4f259a2 | ||
|
|
bdfd042d70 | ||
|
|
a993420676 | ||
|
|
fb6a901374 | ||
|
|
25c2267a89 | ||
|
|
69ea15f73a | ||
|
|
b7ae044e65 | ||
|
|
d8d237f6f2 | ||
|
|
18e1d03a89 | ||
|
|
3947f2315d | ||
|
|
cb9e50b2ea | ||
|
|
e620bb9512 | ||
|
|
8b04fe7633 | ||
|
|
2cb747651b | ||
|
|
46897aab4f | ||
|
|
892787cb1a | ||
|
|
be77e14db9 | ||
|
|
34af7501fa | ||
|
|
183ef34422 | ||
|
|
637936cb9f | ||
|
|
fc59b0ab77 | ||
|
|
d8b312674d | ||
|
|
ebae7229c1 | ||
|
|
3df8cda110 | ||
|
|
43cf11aa35 | ||
|
|
21f0a7e020 | ||
|
|
cb6d2cbd2d | ||
|
|
ce51025e7c | ||
|
|
5a054e5150 | ||
|
|
98f003f71a | ||
|
|
b9efcace79 | ||
|
|
1aaa8de1f9 | ||
|
|
5e2e190f3e | ||
|
|
828434058f | ||
|
|
dfbf5fc9fa | ||
|
|
8029cd3ebb | ||
|
|
e970d7a6aa | ||
|
|
350e795640 | ||
|
|
f5bda652c4 | ||
|
|
e8189cd0f6 | ||
|
|
18231fedef | ||
|
|
935c7231eb | ||
|
|
da2c7db0df | ||
|
|
83d98ac92d | ||
|
|
b997b12d27 | ||
|
|
6887e790c8 | ||
|
|
373a1f72bf | ||
|
|
0d43b06042 | ||
|
|
ced9868357 | ||
|
|
23c867f946 | ||
|
|
42c233c74e | ||
|
|
1f8e74f3a8 | ||
|
|
d6835f8dd6 | ||
|
|
86940e96d5 | ||
|
|
75d90c8e4c | ||
|
|
ecbc0538f6 | ||
|
|
2a26760911 | ||
|
|
19b6808602 | ||
|
|
c2202be0f8 | ||
|
|
9729ae52a3 | ||
|
|
7cc69f30c4 | ||
|
|
e33706ab25 | ||
|
|
4018b7e2d5 | ||
|
|
8425d76198 | ||
|
|
9bf009c4f8 | ||
|
|
a6bde0943e | ||
|
|
db1ebe2559 | ||
|
|
dbebc4774f | ||
|
|
0fbc4545d1 | ||
|
|
5d4bef5478 | ||
|
|
cac3a3e945 | ||
|
|
bca569da42 | ||
|
|
a82303ccd1 | ||
|
|
a2dd2ddd55 | ||
|
|
5835c4b21d | ||
|
|
d8e6808d77 | ||
|
|
9e58e31de0 | ||
|
|
77602aff88 | ||
|
|
1ffd443d5a | ||
|
|
1dc5a624a7 | ||
|
|
3f878d63a5 | ||
|
|
a5882ae162 | ||
|
|
c74028d08d | ||
|
|
3f13851be5 | ||
|
|
ea446fd4a3 | ||
|
|
585b5201f1 | ||
|
|
8d98885cda | ||
|
|
da9418c1b2 | ||
|
|
9dba930a85 | ||
|
|
7722cb3ffa | ||
|
|
3e6819c718 | ||
|
|
41fb1e5106 | ||
|
|
64d5ec12e2 | ||
|
|
d409623086 | ||
|
|
9af28607c9 | ||
|
|
81a8ebde22 | ||
|
|
8055b70ab1 | ||
|
|
05bbf71b6d | ||
|
|
8bf88f4cb2 | ||
|
|
edf5ee0cc4 | ||
|
|
86089ec03a | ||
|
|
29d6783471 | ||
|
|
236e21efcb | ||
|
|
30ec203eff | ||
|
|
a6a4cd5667 | ||
|
|
3a5bbcf2a8 | ||
|
|
449074e73f | ||
|
|
2fc97212a7 | ||
|
|
4519ce26e2 | ||
|
|
49d9649b8e |
192
.ci/azure-pipelines.yml
Normal file
192
.ci/azure-pipelines.yml
Normal file
@@ -0,0 +1,192 @@
|
||||
name: $(Date:yyyyMMdd)$(Rev:.r)
|
||||
|
||||
variables:
|
||||
- name: TestProjects
|
||||
value: 'Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj'
|
||||
- name: RestoreBuildProjects
|
||||
value: 'Jellyfin.Server/Jellyfin.Server.csproj'
|
||||
|
||||
pr:
|
||||
autoCancel: true
|
||||
|
||||
trigger:
|
||||
batch: true
|
||||
|
||||
jobs:
|
||||
- job: main_build
|
||||
displayName: Main Build
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
strategy:
|
||||
matrix:
|
||||
release:
|
||||
BuildConfiguration: Release
|
||||
debug:
|
||||
BuildConfiguration: Debug
|
||||
maxParallel: 2
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
submodules: true
|
||||
persistCredentials: false
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Restore
|
||||
inputs:
|
||||
command: restore
|
||||
projects: '$(RestoreBuildProjects)'
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Build
|
||||
inputs:
|
||||
projects: '$(RestoreBuildProjects)'
|
||||
arguments: '--configuration $(BuildConfiguration)'
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Test
|
||||
inputs:
|
||||
command: test
|
||||
projects: '$(RestoreBuildProjects)'
|
||||
arguments: '--configuration $(BuildConfiguration)'
|
||||
enabled: false
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Publish
|
||||
inputs:
|
||||
command: publish
|
||||
publishWebProjects: false
|
||||
projects: '$(RestoreBuildProjects)'
|
||||
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
|
||||
displayName: 'Publish Artifact Naming'
|
||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||
inputs:
|
||||
PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll'
|
||||
artifactName: 'Jellyfin.Naming'
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish Artifact Controller'
|
||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||
inputs:
|
||||
PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
|
||||
artifactName: 'Jellyfin.Controller'
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish Artifact Model'
|
||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||
inputs:
|
||||
PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
|
||||
artifactName: 'Jellyfin.Model'
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish Artifact Common'
|
||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||
inputs:
|
||||
PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
|
||||
artifactName: 'Jellyfin.Common'
|
||||
|
||||
- job: dotnet_compat
|
||||
displayName: Compatibility Check
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
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:
|
||||
matrix:
|
||||
Naming:
|
||||
NugetPackageName: Jellyfin.Naming
|
||||
AssemblyFileName: Emby.Naming.dll
|
||||
Controller:
|
||||
NugetPackageName: Jellyfin.Controller
|
||||
AssemblyFileName: MediaBrowser.Controller.dll
|
||||
Model:
|
||||
NugetPackageName: Jellyfin.Model
|
||||
AssemblyFileName: MediaBrowser.Model.dll
|
||||
Common:
|
||||
NugetPackageName: Jellyfin.Common
|
||||
AssemblyFileName: MediaBrowser.Common.dll
|
||||
maxParallel: 2
|
||||
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
|
||||
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'
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: Copy Artifact Assembly to new-release folder
|
||||
inputs:
|
||||
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts # Optional
|
||||
contents: '**/*.dll'
|
||||
targetFolder: $(System.ArtifactsDirectory)/new-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
|
||||
userRepository: EraYaN/dotnet-compatibility
|
||||
defaultVersionType: 'latest' # Options: latest, specificVersion, specificTag
|
||||
#version: # Required when defaultVersionType != Latest
|
||||
itemPattern: '**-ci.zip' # Optional
|
||||
downloadPath: '$(System.ArtifactsDirectory)'
|
||||
|
||||
- task: ExtractFiles@1
|
||||
displayName: Extract ABI compatibility check tool
|
||||
inputs:
|
||||
archiveFilePatterns: '$(System.ArtifactsDirectory)/*-ci.zip'
|
||||
destinationFolder: $(System.ArtifactsDirectory)/tools
|
||||
cleanDestinationFolder: true
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: Execute ABI compatibility check tool
|
||||
inputs:
|
||||
script: 'dotnet tools/CompatibilityCheckerCoreCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName)'
|
||||
workingDirectory: $(System.ArtifactsDirectory) # Optional
|
||||
#failOnStderr: false # Optional
|
||||
|
||||
|
||||
8
.copr/Makefile
Normal file
8
.copr/Makefile
Normal file
@@ -0,0 +1,8 @@
|
||||
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)"
|
||||
@@ -8,3 +8,4 @@ README.md
|
||||
deployment/*/dist
|
||||
deployment/*/pkg-dist
|
||||
deployment/collect-dist/
|
||||
ci/
|
||||
|
||||
22
.drone.yml
22
.drone.yml
@@ -1,12 +1,30 @@
|
||||
---
|
||||
kind: pipeline
|
||||
name: build
|
||||
name: build-debug
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init --recursive
|
||||
|
||||
- name: build
|
||||
image: microsoft/dotnet:2-sdk
|
||||
commands:
|
||||
- dotnet publish --configuration release --output /release Jellyfin.Server
|
||||
- dotnet publish "Jellyfin.Server" --configuration Debug --output "../ci/ci-debug"
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: build-release
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init --recursive
|
||||
|
||||
- name: build
|
||||
image: microsoft/dotnet:2-sdk
|
||||
commands:
|
||||
- dotnet publish "Jellyfin.Server" --configuration Release --output "../ci/ci-release"
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ insert_final_newline = true
|
||||
end_of_line = lf
|
||||
max_line_length = null
|
||||
|
||||
# YAML indentation
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
# XML indentation
|
||||
[*.{csproj,xml}]
|
||||
indent_size = 2
|
||||
@@ -55,15 +59,77 @@ dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
###############################
|
||||
# Naming Conventions #
|
||||
###############################
|
||||
# Style Definitions
|
||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||
# Use PascalCase for constant fields
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
|
||||
dotnet_naming_symbols.constant_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.constant_fields.required_modifiers = const
|
||||
# Style Definitions (From Roslyn)
|
||||
|
||||
# Non-private static fields are PascalCase
|
||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
|
||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
|
||||
|
||||
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected
|
||||
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
|
||||
|
||||
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
|
||||
|
||||
# Constants are PascalCase
|
||||
dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
|
||||
dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
|
||||
|
||||
dotnet_naming_symbols.constants.applicable_kinds = field, local
|
||||
dotnet_naming_symbols.constants.required_modifiers = const
|
||||
|
||||
dotnet_naming_style.constant_style.capitalization = pascal_case
|
||||
|
||||
# Static fields are camelCase and start with s_
|
||||
dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion
|
||||
dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
|
||||
dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
|
||||
|
||||
dotnet_naming_symbols.static_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.static_fields.required_modifiers = static
|
||||
|
||||
dotnet_naming_style.static_field_style.capitalization = camel_case
|
||||
dotnet_naming_style.static_field_style.required_prefix = _
|
||||
|
||||
# Instance fields are camelCase and start with _
|
||||
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
|
||||
dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
|
||||
dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
|
||||
|
||||
dotnet_naming_symbols.instance_fields.applicable_kinds = field
|
||||
|
||||
dotnet_naming_style.instance_field_style.capitalization = camel_case
|
||||
dotnet_naming_style.instance_field_style.required_prefix = _
|
||||
|
||||
# Locals and parameters are camelCase
|
||||
dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
|
||||
dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
|
||||
dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
|
||||
|
||||
dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
|
||||
|
||||
dotnet_naming_style.camel_case_style.capitalization = camel_case
|
||||
|
||||
# Local functions are PascalCase
|
||||
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
|
||||
dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
|
||||
|
||||
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
|
||||
|
||||
dotnet_naming_style.local_function_style.capitalization = pascal_case
|
||||
|
||||
# By default, name items with PascalCase
|
||||
dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
|
||||
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
|
||||
|
||||
dotnet_naming_symbols.all_members.applicable_kinds = *
|
||||
|
||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||
|
||||
###############################
|
||||
# C# Coding Conventions #
|
||||
###############################
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -30,6 +30,7 @@ assignees: ''
|
||||
- OS: [e.g. Docker, Debian, Windows]
|
||||
- Browser: [e.g. Firefox, Chrome, Safari]
|
||||
- Jellyfin Version: [e.g. 10.0.1]
|
||||
- Reverse proxy: [e.g. no, nginx, apache, etc.]
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context about the problem here. -->
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -264,3 +264,8 @@ deployment/**/pkg-dist-tmp/
|
||||
deployment/collect-dist/
|
||||
|
||||
jellyfin_version.ini
|
||||
|
||||
ci/
|
||||
|
||||
# Doxygen
|
||||
doc/
|
||||
1
.gitmodules
vendored
1
.gitmodules
vendored
@@ -1,3 +1,4 @@
|
||||
[submodule "MediaBrowser.WebDashboard/jellyfin-web"]
|
||||
path = MediaBrowser.WebDashboard/jellyfin-web
|
||||
url = https://github.com/jellyfin/jellyfin-web.git
|
||||
branch = .
|
||||
|
||||
@@ -165,7 +165,7 @@ namespace BDInfo
|
||||
foreach (var file in files)
|
||||
{
|
||||
PlaylistFiles.Add(
|
||||
file.Name.ToUpper(), new TSPlaylistFile(this, file, _fileSystem));
|
||||
file.Name.ToUpper(), new TSPlaylistFile(this, file));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ namespace BDInfo
|
||||
foreach (var file in files)
|
||||
{
|
||||
StreamClipFiles.Add(
|
||||
file.Name.ToUpper(), new TSStreamClipFile(file, _fileSystem));
|
||||
file.Name.ToUpper(), new TSStreamClipFile(file));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016 CinemaSquid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
||||
[assembly: AssemblyProduct("Jellyfin Server")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016 CinemaSquid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: NeutralResourcesLanguage("en")]
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace BDInfo
|
||||
{
|
||||
public class TSPlaylistFile
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private FileSystemMetadata FileInfo = null;
|
||||
public string FileType = null;
|
||||
public bool IsInitialized = false;
|
||||
@@ -64,21 +63,19 @@ namespace BDInfo
|
||||
new List<TSGraphicsStream>();
|
||||
|
||||
public TSPlaylistFile(BDROM bdrom,
|
||||
FileSystemMetadata fileInfo, IFileSystem fileSystem)
|
||||
FileSystemMetadata fileInfo)
|
||||
{
|
||||
BDROM = bdrom;
|
||||
FileInfo = fileInfo;
|
||||
_fileSystem = fileSystem;
|
||||
Name = fileInfo.Name.ToUpper();
|
||||
}
|
||||
|
||||
public TSPlaylistFile(BDROM bdrom,
|
||||
string name,
|
||||
List<TSStreamClip> clips, IFileSystem fileSystem)
|
||||
List<TSStreamClip> clips)
|
||||
{
|
||||
BDROM = bdrom;
|
||||
Name = name;
|
||||
_fileSystem = fileSystem;
|
||||
IsCustom = true;
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace BDInfo
|
||||
{
|
||||
public class TSStreamClipFile
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
public FileSystemMetadata FileInfo = null;
|
||||
public string FileType = null;
|
||||
public bool IsValid = false;
|
||||
@@ -37,10 +36,9 @@ namespace BDInfo
|
||||
public Dictionary<ushort, TSStream> Streams =
|
||||
new Dictionary<ushort, TSStream>();
|
||||
|
||||
public TSStreamClipFile(FileSystemMetadata fileInfo, IFileSystem fileSystem)
|
||||
public TSStreamClipFile(FileSystemMetadata fileInfo)
|
||||
{
|
||||
FileInfo = fileInfo;
|
||||
_fileSystem = fileSystem;
|
||||
Name = fileInfo.Name.ToUpper();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
- [LogicalPhallacy](https://github.com/LogicalPhallacy/)
|
||||
- [RazeLighter777](https://github.com/RazeLighter777)
|
||||
- [WillWill56](https://github.com/WillWill56)
|
||||
- [Liggy](https://github.com/Liggy)
|
||||
- [fruhnow](https://github.com/fruhnow)
|
||||
- [Lynxy](https://github.com/Lynxy)
|
||||
- [fasheng](https://github.com/fasheng)
|
||||
- [ploughpuff](https://github.com/ploughpuff)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
||||
25
Dockerfile
25
Dockerfile
@@ -1,27 +1,34 @@
|
||||
ARG DOTNET_VERSION=2
|
||||
ARG DOTNET_VERSION=2.2
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk as builder
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
RUN dotnet publish \
|
||||
--configuration release \
|
||||
--output /jellyfin \
|
||||
Jellyfin.Server
|
||||
RUN bash -c "source deployment/common.build.sh && \
|
||||
build_jellyfin Jellyfin.Server Release linux-x64 /jellyfin"
|
||||
|
||||
FROM jellyfin/ffmpeg as ffmpeg
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime
|
||||
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}
|
||||
# libfontconfig1 is required for Skia
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
libfontconfig1 \
|
||||
&& apt-get clean autoclean \
|
||||
&& apt-get autoremove \
|
||||
&& rm -rf /var/lib/{apt,dpkg,cache,log} \
|
||||
&& 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
|
||||
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
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config --cachedir /cache
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
||||
--datadir /config \
|
||||
--cachedir /cache \
|
||||
--ffmpeg /usr/local/bin/ffmpeg
|
||||
|
||||
@@ -8,7 +8,7 @@ FROM alpine as qemu_extract
|
||||
COPY --from=qemu /usr/bin qemu-arm-static.tar.gz
|
||||
RUN tar -xzvf qemu-arm-static.tar.gz
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch as builder
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
@@ -17,20 +17,27 @@ 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 dotnet publish \
|
||||
-r linux-arm \
|
||||
--configuration release \
|
||||
--output /jellyfin \
|
||||
Jellyfin.Server
|
||||
RUN bash -c "source deployment/common.build.sh && \
|
||||
build_jellyfin Jellyfin.Server Release linux-arm /jellyfin"
|
||||
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm32v7
|
||||
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm32v7
|
||||
COPY --from=qemu_extract 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
|
||||
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
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config --cachedir /cache
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
||||
--datadir /config \
|
||||
--cachedir /cache \
|
||||
--ffmpeg /usr/bin/ffmpeg
|
||||
|
||||
@@ -9,7 +9,7 @@ COPY --from=qemu /usr/bin qemu-aarch64-static.tar.gz
|
||||
RUN tar -xzvf qemu-aarch64-static.tar.gz
|
||||
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch as builder
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
@@ -18,20 +18,27 @@ 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 dotnet publish \
|
||||
-r linux-arm64 \
|
||||
--configuration release \
|
||||
--output /jellyfin \
|
||||
Jellyfin.Server
|
||||
RUN bash -c "source deployment/common.build.sh && \
|
||||
build_jellyfin Jellyfin.Server Release linux-arm64 /jellyfin"
|
||||
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm64v8
|
||||
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm64v8
|
||||
COPY --from=qemu_extract 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
|
||||
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
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config --cachedir /cache
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
||||
--datadir /config \
|
||||
--cachedir /cache \
|
||||
--ffmpeg /usr/bin/ffmpeg
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
namespace DvdLib.Ifo
|
||||
{
|
||||
public enum AudioCodec
|
||||
{
|
||||
AC3 = 0,
|
||||
MPEG1 = 2,
|
||||
MPEG2ext = 3,
|
||||
LPCM = 4,
|
||||
DTS = 6,
|
||||
}
|
||||
|
||||
public enum ApplicationMode
|
||||
{
|
||||
Unspecified = 0,
|
||||
Karaoke = 1,
|
||||
Surround = 2,
|
||||
}
|
||||
|
||||
public class AudioAttributes
|
||||
{
|
||||
public readonly AudioCodec Codec;
|
||||
public readonly bool MultichannelExtensionPresent;
|
||||
public readonly ApplicationMode Mode;
|
||||
public readonly byte QuantDRC;
|
||||
public readonly byte SampleRate;
|
||||
public readonly byte Channels;
|
||||
public readonly ushort LanguageCode;
|
||||
public readonly byte LanguageExtension;
|
||||
public readonly byte CodeExtension;
|
||||
}
|
||||
|
||||
public class MultiChannelExtension
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -26,17 +26,17 @@ namespace DvdLib.Ifo
|
||||
|
||||
if (vmgPath == null)
|
||||
{
|
||||
var allIfos = allFiles.Where(i => string.Equals(i.Extension, ".ifo", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
foreach (var ifo in allIfos)
|
||||
foreach (var ifo in allFiles)
|
||||
{
|
||||
var num = ifo.Name.Split('_').ElementAtOrDefault(1);
|
||||
var numbersRead = new List<ushort>();
|
||||
if (!string.Equals(ifo.Extension, ".ifo", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(num) && ushort.TryParse(num, out var ifoNumber) && !numbersRead.Contains(ifoNumber))
|
||||
var nums = ifo.Name.Split(new [] { '_' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber))
|
||||
{
|
||||
ReadVTS(ifoNumber, ifo.FullName);
|
||||
numbersRead.Add(ifoNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ namespace DvdLib.Ifo
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadVTS(ushort vtsNum, List<FileSystemMetadata> allFiles)
|
||||
private void ReadVTS(ushort vtsNum, IEnumerable<FileSystemMetadata> allFiles)
|
||||
{
|
||||
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DvdLib.Ifo
|
||||
{
|
||||
public class ProgramChainCommandTable
|
||||
{
|
||||
public readonly ushort LastByteAddress;
|
||||
public readonly List<VirtualMachineCommand> PreCommands;
|
||||
public readonly List<VirtualMachineCommand> PostCommands;
|
||||
public readonly List<VirtualMachineCommand> CellCommands;
|
||||
}
|
||||
|
||||
public class VirtualMachineCommand
|
||||
{
|
||||
public readonly byte[] Command;
|
||||
}
|
||||
}
|
||||
@@ -25,13 +25,10 @@ namespace DvdLib.Ifo
|
||||
public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
|
||||
|
||||
private ushort _nextProgramNumber;
|
||||
public readonly ProgramChain Next;
|
||||
|
||||
private ushort _prevProgramNumber;
|
||||
public readonly ProgramChain Previous;
|
||||
|
||||
private ushort _goupProgramNumber;
|
||||
public readonly ProgramChain Goup; // ?? maybe Group
|
||||
|
||||
public ProgramPlaybackMode PlaybackMode { get; private set; }
|
||||
public uint ProgramCount { get; private set; }
|
||||
@@ -40,7 +37,6 @@ namespace DvdLib.Ifo
|
||||
public byte[] Palette { get; private set; } // 16*4 entries
|
||||
|
||||
private ushort _commandTableOffset;
|
||||
public readonly ProgramChainCommandTable CommandTable;
|
||||
|
||||
private ushort _programMapOffset;
|
||||
private ushort _cellPlaybackOffset;
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
namespace DvdLib.Ifo
|
||||
{
|
||||
public enum VideoCodec
|
||||
{
|
||||
MPEG1 = 0,
|
||||
MPEG2 = 1,
|
||||
}
|
||||
|
||||
public enum VideoFormat
|
||||
{
|
||||
NTSC = 0,
|
||||
PAL = 1,
|
||||
}
|
||||
|
||||
public enum AspectRatio
|
||||
{
|
||||
ar4to3 = 0,
|
||||
ar16to9 = 3
|
||||
}
|
||||
|
||||
public enum FilmMode
|
||||
{
|
||||
None = -1,
|
||||
Camera = 0,
|
||||
Film = 1,
|
||||
}
|
||||
|
||||
public class VideoAttributes
|
||||
{
|
||||
public readonly VideoCodec Codec;
|
||||
public readonly VideoFormat Format;
|
||||
public readonly AspectRatio Aspect;
|
||||
public readonly bool AutomaticPanScan;
|
||||
public readonly bool AutomaticLetterBox;
|
||||
public readonly bool Line21CCField1;
|
||||
public readonly bool Line21CCField2;
|
||||
public readonly int Width;
|
||||
public readonly int Height;
|
||||
public readonly bool Letterboxed;
|
||||
public readonly FilmMode FilmMode;
|
||||
|
||||
public VideoAttributes()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
||||
[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")]
|
||||
|
||||
@@ -136,7 +136,7 @@ namespace Emby.Dlna.Api
|
||||
{
|
||||
var url = Request.AbsoluteUri;
|
||||
var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
|
||||
var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers.ToDictionary(), request.UuId, serverAddress);
|
||||
var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, request.UuId, serverAddress);
|
||||
|
||||
var cacheLength = TimeSpan.FromDays(1);
|
||||
var cacheKey = Request.RawUrl.GetMD5();
|
||||
@@ -147,21 +147,21 @@ namespace Emby.Dlna.Api
|
||||
|
||||
public object Get(GetContentDirectory request)
|
||||
{
|
||||
var xml = ContentDirectory.GetServiceXml(Request.Headers.ToDictionary());
|
||||
var xml = ContentDirectory.GetServiceXml();
|
||||
|
||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||
}
|
||||
|
||||
public object Get(GetMediaReceiverRegistrar request)
|
||||
{
|
||||
var xml = MediaReceiverRegistrar.GetServiceXml(Request.Headers.ToDictionary());
|
||||
var xml = MediaReceiverRegistrar.GetServiceXml();
|
||||
|
||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||
}
|
||||
|
||||
public object Get(GetConnnectionManager request)
|
||||
{
|
||||
var xml = ConnectionManager.GetServiceXml(Request.Headers.ToDictionary());
|
||||
var xml = ConnectionManager.GetServiceXml();
|
||||
|
||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||
}
|
||||
@@ -193,7 +193,7 @@ namespace Emby.Dlna.Api
|
||||
|
||||
return service.ProcessControlRequest(new ControlRequest
|
||||
{
|
||||
Headers = Request.Headers.ToDictionary(),
|
||||
Headers = Request.Headers,
|
||||
InputXml = requestStream,
|
||||
TargetServerUuId = id,
|
||||
RequestedUrl = Request.AbsoluteUri
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Emby.Dlna.Configuration
|
||||
public bool EnableServer { get; set; }
|
||||
public bool EnableDebugLog { get; set; }
|
||||
public bool BlastAliveMessages { get; set; }
|
||||
public bool SendOnlyMatchedHost { get; set; }
|
||||
public int ClientDiscoveryIntervalSeconds { get; set; }
|
||||
public int BlastAliveMessageIntervalSeconds { get; set; }
|
||||
public string DefaultUserId { get; set; }
|
||||
@@ -16,6 +17,7 @@ namespace Emby.Dlna.Configuration
|
||||
EnablePlayTo = true;
|
||||
EnableServer = true;
|
||||
BlastAliveMessages = true;
|
||||
SendOnlyMatchedHost = true;
|
||||
ClientDiscoveryIntervalSeconds = 60;
|
||||
BlastAliveMessageIntervalSeconds = 1800;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Service;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Xml;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Dlna.ConnectionManager
|
||||
@@ -13,18 +11,16 @@ namespace Emby.Dlna.ConnectionManager
|
||||
private readonly IDlnaManager _dlna;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
|
||||
|
||||
public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
|
||||
public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient)
|
||||
: base(logger, httpClient)
|
||||
{
|
||||
_dlna = dlna;
|
||||
_config = config;
|
||||
_logger = logger;
|
||||
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
|
||||
}
|
||||
|
||||
public string GetServiceXml(IDictionary<string, string> headers)
|
||||
public string GetServiceXml()
|
||||
{
|
||||
return new ConnectionManagerXmlBuilder().GetXml();
|
||||
}
|
||||
@@ -34,7 +30,7 @@ namespace Emby.Dlna.ConnectionManager
|
||||
var profile = _dlna.GetProfile(request.Headers) ??
|
||||
_dlna.GetDefaultProfile();
|
||||
|
||||
return new ControlHandler(_config, _logger, XmlReaderSettingsFactory, profile).ProcessControlRequest(request);
|
||||
return new ControlHandler(_config, _logger, profile).ProcessControlRequest(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using Emby.Dlna.Service;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Xml;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Dlna.ConnectionManager
|
||||
@@ -32,7 +31,8 @@ namespace Emby.Dlna.ConnectionManager
|
||||
};
|
||||
}
|
||||
|
||||
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory, DeviceProfile profile) : base(config, logger, xmlReaderSettingsFactory)
|
||||
public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile)
|
||||
: base(config, logger)
|
||||
{
|
||||
_profile = profile;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.TV;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Xml;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Dlna.ContentDirectory
|
||||
@@ -28,7 +27,6 @@ namespace Emby.Dlna.ContentDirectory
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IUserViewManager _userViewManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
|
||||
private readonly ITVSeriesManager _tvSeriesManager;
|
||||
|
||||
public ContentDirectory(IDlnaManager dlna,
|
||||
@@ -38,7 +36,12 @@ namespace Emby.Dlna.ContentDirectory
|
||||
IServerConfigurationManager config,
|
||||
IUserManager userManager,
|
||||
ILogger logger,
|
||||
IHttpClient httpClient, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager)
|
||||
IHttpClient httpClient,
|
||||
ILocalizationManager localization,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IUserViewManager userViewManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
ITVSeriesManager tvSeriesManager)
|
||||
: base(logger, httpClient)
|
||||
{
|
||||
_dlna = dlna;
|
||||
@@ -51,7 +54,6 @@ namespace Emby.Dlna.ContentDirectory
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_userViewManager = userViewManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
|
||||
_tvSeriesManager = tvSeriesManager;
|
||||
}
|
||||
|
||||
@@ -65,7 +67,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
}
|
||||
}
|
||||
|
||||
public string GetServiceXml(IDictionary<string, string> headers)
|
||||
public string GetServiceXml()
|
||||
{
|
||||
return new ContentDirectoryXmlBuilder().GetXml();
|
||||
}
|
||||
@@ -94,7 +96,6 @@ namespace Emby.Dlna.ContentDirectory
|
||||
_mediaSourceManager,
|
||||
_userViewManager,
|
||||
_mediaEncoder,
|
||||
XmlReaderSettingsFactory,
|
||||
_tvSeriesManager)
|
||||
.ProcessControlRequest(request);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Xml;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Dlna.ContentDirectory
|
||||
@@ -51,8 +50,22 @@ namespace Emby.Dlna.ContentDirectory
|
||||
|
||||
private readonly DeviceProfile _profile;
|
||||
|
||||
public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager)
|
||||
: base(config, logger, xmlReaderSettingsFactory)
|
||||
public ControlHandler(
|
||||
ILogger logger,
|
||||
ILibraryManager libraryManager,
|
||||
DeviceProfile profile,
|
||||
string serverAddress,
|
||||
string accessToken,
|
||||
IImageProcessor imageProcessor,
|
||||
IUserDataManager userDataManager,
|
||||
User user, int systemUpdateId,
|
||||
IServerConfigurationManager config,
|
||||
ILocalizationManager localization,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IUserViewManager userViewManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
ITVSeriesManager tvSeriesManager)
|
||||
: base(config, logger)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_userDataManager = userDataManager;
|
||||
@@ -260,7 +273,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
|
||||
if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue)
|
||||
{
|
||||
var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount));
|
||||
var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount);
|
||||
|
||||
_didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
|
||||
}
|
||||
@@ -273,7 +286,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
}
|
||||
else
|
||||
{
|
||||
var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount));
|
||||
var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount);
|
||||
totalCount = childrenResult.TotalRecordCount;
|
||||
|
||||
provided = childrenResult.Items.Length;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
public class ControlRequest
|
||||
{
|
||||
public IDictionary<string, string> Headers { get; set; }
|
||||
public IHeaderDictionary Headers { get; set; }
|
||||
|
||||
public Stream InputXml { get; set; }
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Emby.Dlna
|
||||
|
||||
public ControlRequest()
|
||||
{
|
||||
Headers = new Dictionary<string, string>();
|
||||
Headers = new HeaderDictionary();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -818,10 +818,9 @@ namespace Emby.Dlna.Didl
|
||||
{
|
||||
AddCommonFields(item, itemStubType, context, writer, filter);
|
||||
|
||||
var hasArtists = item as IHasArtist;
|
||||
var hasAlbumArtists = item as IHasAlbumArtist;
|
||||
|
||||
if (hasArtists != null)
|
||||
if (item is IHasArtist hasArtists)
|
||||
{
|
||||
foreach (var artist in hasArtists.Artists)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
@@ -15,9 +16,10 @@ using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Reflection;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
@@ -29,7 +31,7 @@ namespace Emby.Dlna
|
||||
private readonly ILogger _logger;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IAssemblyInfo _assemblyInfo;
|
||||
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
|
||||
|
||||
private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
|
||||
|
||||
@@ -38,7 +40,8 @@ namespace Emby.Dlna
|
||||
IFileSystem fileSystem,
|
||||
IApplicationPaths appPaths,
|
||||
ILoggerFactory loggerFactory,
|
||||
IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IAssemblyInfo assemblyInfo)
|
||||
IJsonSerializer jsonSerializer,
|
||||
IServerApplicationHost appHost)
|
||||
{
|
||||
_xmlSerializer = xmlSerializer;
|
||||
_fileSystem = fileSystem;
|
||||
@@ -46,7 +49,6 @@ namespace Emby.Dlna
|
||||
_logger = loggerFactory.CreateLogger("Dlna");
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_appHost = appHost;
|
||||
_assemblyInfo = assemblyInfo;
|
||||
}
|
||||
|
||||
public async Task InitProfilesAsync()
|
||||
@@ -203,16 +205,13 @@ namespace Emby.Dlna
|
||||
}
|
||||
}
|
||||
|
||||
public DeviceProfile GetProfile(IDictionary<string, string> headers)
|
||||
public DeviceProfile GetProfile(IHeaderDictionary headers)
|
||||
{
|
||||
if (headers == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(headers));
|
||||
}
|
||||
|
||||
// Convert to case insensitive
|
||||
headers = new Dictionary<string, string>(headers, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
|
||||
|
||||
if (profile != null)
|
||||
@@ -228,12 +227,12 @@ namespace Emby.Dlna
|
||||
return profile;
|
||||
}
|
||||
|
||||
private bool IsMatch(IDictionary<string, string> headers, DeviceIdentification profileInfo)
|
||||
private bool IsMatch(IHeaderDictionary headers, DeviceIdentification profileInfo)
|
||||
{
|
||||
return profileInfo.Headers.Any(i => IsMatch(headers, i));
|
||||
}
|
||||
|
||||
private bool IsMatch(IDictionary<string, string> headers, HttpHeaderInfo header)
|
||||
private bool IsMatch(IHeaderDictionary headers, HttpHeaderInfo header)
|
||||
{
|
||||
// Handle invalid user setup
|
||||
if (string.IsNullOrEmpty(header.Name))
|
||||
@@ -241,14 +240,14 @@ namespace Emby.Dlna
|
||||
return false;
|
||||
}
|
||||
|
||||
if (headers.TryGetValue(header.Name, out string value))
|
||||
if (headers.TryGetValue(header.Name, out StringValues value))
|
||||
{
|
||||
switch (header.Match)
|
||||
{
|
||||
case HeaderMatchType.Equals:
|
||||
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
|
||||
case HeaderMatchType.Substring:
|
||||
var isMatch = value.IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
|
||||
var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
|
||||
//_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
|
||||
return isMatch;
|
||||
case HeaderMatchType.Regex:
|
||||
@@ -366,15 +365,18 @@ namespace Emby.Dlna
|
||||
|
||||
var systemProfilesPath = SystemProfilesPath;
|
||||
|
||||
foreach (var name in _assemblyInfo.GetManifestResourceNames(GetType())
|
||||
.Where(i => i.StartsWith(namespaceName))
|
||||
.ToList())
|
||||
foreach (var name in _assembly.GetManifestResourceNames())
|
||||
{
|
||||
if (!name.StartsWith(namespaceName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var filename = Path.GetFileName(name).Substring(namespaceName.Length);
|
||||
|
||||
var path = Path.Combine(systemProfilesPath, filename);
|
||||
|
||||
using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), name))
|
||||
using (var stream = _assembly.GetManifestResourceStream(name))
|
||||
{
|
||||
var fileInfo = _fileSystem.GetFileInfo(path);
|
||||
|
||||
@@ -491,7 +493,7 @@ namespace Emby.Dlna
|
||||
internal string Path { get; set; }
|
||||
}
|
||||
|
||||
public string GetServerDescriptionXml(IDictionary<string, string> headers, string serverUuId, string serverAddress)
|
||||
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
|
||||
{
|
||||
var profile = GetProfile(headers) ??
|
||||
GetDefaultProfile();
|
||||
@@ -512,7 +514,7 @@ namespace Emby.Dlna
|
||||
return new ImageStream
|
||||
{
|
||||
Format = format,
|
||||
Stream = _assemblyInfo.GetManifestResourceStream(GetType(), resource)
|
||||
Stream = _assembly.GetManifestResourceStream(resource)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,4 +58,9 @@
|
||||
<EmbeddedResource Include="Profiles\Xml\Xbox One.xml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
public interface IUpnpService
|
||||
@@ -7,9 +5,8 @@ namespace Emby.Dlna
|
||||
/// <summary>
|
||||
/// Gets the content directory XML.
|
||||
/// </summary>
|
||||
/// <param name="headers">The headers.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
string GetServiceXml(IDictionary<string, string> headers);
|
||||
string GetServiceXml();
|
||||
|
||||
/// <summary>
|
||||
/// Processes the control request.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 6.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.PlayTo;
|
||||
@@ -20,10 +19,10 @@ using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.System;
|
||||
using MediaBrowser.Model.Xml;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Rssdp;
|
||||
using Rssdp.Infrastructure;
|
||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||
|
||||
namespace Emby.Dlna.Main
|
||||
{
|
||||
@@ -48,9 +47,8 @@ namespace Emby.Dlna.Main
|
||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||
|
||||
private SsdpDevicePublisher _Publisher;
|
||||
|
||||
|
||||
private readonly ISocketFactory _socketFactory;
|
||||
private readonly IEnvironmentInfo _environmentInfo;
|
||||
private readonly INetworkManager _networkManager;
|
||||
|
||||
private ISsdpCommunicationsServer _communicationsServer;
|
||||
@@ -76,10 +74,8 @@ namespace Emby.Dlna.Main
|
||||
IDeviceDiscovery deviceDiscovery,
|
||||
IMediaEncoder mediaEncoder,
|
||||
ISocketFactory socketFactory,
|
||||
IEnvironmentInfo environmentInfo,
|
||||
INetworkManager networkManager,
|
||||
IUserViewManager userViewManager,
|
||||
IXmlReaderSettingsFactory xmlReaderSettingsFactory,
|
||||
ITVSeriesManager tvSeriesManager)
|
||||
{
|
||||
_config = config;
|
||||
@@ -96,11 +92,11 @@ namespace Emby.Dlna.Main
|
||||
_deviceDiscovery = deviceDiscovery;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_socketFactory = socketFactory;
|
||||
_environmentInfo = environmentInfo;
|
||||
_networkManager = networkManager;
|
||||
_logger = loggerFactory.CreateLogger("Dlna");
|
||||
|
||||
ContentDirectory = new ContentDirectory.ContentDirectory(dlnaManager,
|
||||
ContentDirectory = new ContentDirectory.ContentDirectory(
|
||||
dlnaManager,
|
||||
userDataManager,
|
||||
imageProcessor,
|
||||
libraryManager,
|
||||
@@ -112,12 +108,11 @@ namespace Emby.Dlna.Main
|
||||
mediaSourceManager,
|
||||
userViewManager,
|
||||
mediaEncoder,
|
||||
xmlReaderSettingsFactory,
|
||||
tvSeriesManager);
|
||||
|
||||
ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient, xmlReaderSettingsFactory);
|
||||
ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient);
|
||||
|
||||
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config, xmlReaderSettingsFactory);
|
||||
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config);
|
||||
Current = this;
|
||||
}
|
||||
|
||||
@@ -169,9 +164,10 @@ namespace Emby.Dlna.Main
|
||||
{
|
||||
if (_communicationsServer == null)
|
||||
{
|
||||
var enableMultiSocketBinding = _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows;
|
||||
var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows ||
|
||||
OperatingSystem.Id == OperatingSystemId.Linux;
|
||||
|
||||
_communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
|
||||
_communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding)
|
||||
{
|
||||
IsShared = true
|
||||
};
|
||||
@@ -229,7 +225,7 @@ namespace Emby.Dlna.Main
|
||||
|
||||
try
|
||||
{
|
||||
_Publisher = new SsdpDevicePublisher(_communicationsServer, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion);
|
||||
_Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost);
|
||||
_Publisher.LogFunction = LogMessage;
|
||||
_Publisher.SupportPnpRootDevice = false;
|
||||
|
||||
@@ -245,17 +241,17 @@ namespace Emby.Dlna.Main
|
||||
|
||||
private async Task RegisterServerEndpoints()
|
||||
{
|
||||
var addresses = (await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false)).ToList();
|
||||
var addresses = await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
var udn = CreateUuid(_appHost.SystemId);
|
||||
|
||||
foreach (var address in addresses)
|
||||
{
|
||||
// TODO: Remove this condition on platforms that support it
|
||||
//if (address.AddressFamily == IpAddressFamily.InterNetworkV6)
|
||||
//{
|
||||
// continue;
|
||||
//}
|
||||
if (address.AddressFamily == IpAddressFamily.InterNetworkV6)
|
||||
{
|
||||
// Not support IPv6 right now
|
||||
continue;
|
||||
}
|
||||
|
||||
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
|
||||
|
||||
@@ -268,6 +264,8 @@ namespace Emby.Dlna.Main
|
||||
{
|
||||
CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info.
|
||||
Location = uri, // Must point to the URL that serves your devices UPnP description document.
|
||||
Address = address,
|
||||
SubnetMask = _networkManager.GetLocalIpSubnetMask(address),
|
||||
FriendlyName = "Jellyfin",
|
||||
Manufacturer = "Jellyfin",
|
||||
ModelName = "Jellyfin Server",
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using Emby.Dlna.Service;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Xml;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Dlna.MediaReceiverRegistrar
|
||||
@@ -36,7 +35,8 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
||||
};
|
||||
}
|
||||
|
||||
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(config, logger, xmlReaderSettingsFactory)
|
||||
public ControlHandler(IServerConfigurationManager config, ILogger logger)
|
||||
: base(config, logger)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Service;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Xml;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Dlna.MediaReceiverRegistrar
|
||||
@@ -10,16 +8,14 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
||||
public class MediaReceiverRegistrar : BaseService, IMediaReceiverRegistrar
|
||||
{
|
||||
private readonly IServerConfigurationManager _config;
|
||||
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
|
||||
|
||||
public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
|
||||
public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config)
|
||||
: base(logger, httpClient)
|
||||
{
|
||||
_config = config;
|
||||
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
|
||||
}
|
||||
|
||||
public string GetServiceXml(IDictionary<string, string> headers)
|
||||
public string GetServiceXml()
|
||||
{
|
||||
return new MediaReceiverRegistrarXmlBuilder().GetXml();
|
||||
}
|
||||
@@ -28,7 +24,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
||||
{
|
||||
return new ControlHandler(
|
||||
_config,
|
||||
Logger, XmlReaderSettingsFactory)
|
||||
Logger)
|
||||
.ProcessControlRequest(request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
public class CurrentIdEventArgs : EventArgs
|
||||
{
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Emby.Dlna.Common;
|
||||
using Emby.Dlna.Server;
|
||||
@@ -733,26 +734,21 @@ namespace Emby.Dlna.PlayTo
|
||||
return (true, null);
|
||||
}
|
||||
|
||||
XElement uPnpResponse;
|
||||
XElement uPnpResponse = null;
|
||||
|
||||
// Handle different variations sent back by devices
|
||||
try
|
||||
{
|
||||
uPnpResponse = XElement.Parse(trackString);
|
||||
uPnpResponse = ParseResponse(trackString);
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception ex)
|
||||
{
|
||||
// first try to add a root node with a dlna namesapce
|
||||
try
|
||||
{
|
||||
uPnpResponse = XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + trackString + "</data>");
|
||||
uPnpResponse = uPnpResponse.Descendants().First();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unable to parse xml {0}", trackString);
|
||||
return (true, null);
|
||||
}
|
||||
_logger.LogError(ex, "Uncaught exception while parsing xml");
|
||||
}
|
||||
|
||||
if (uPnpResponse == null)
|
||||
{
|
||||
_logger.LogError("Failed to parse xml: \n {Xml}", trackString);
|
||||
return (true, null);
|
||||
}
|
||||
|
||||
var e = uPnpResponse.Element(uPnpNamespaces.items);
|
||||
@@ -762,6 +758,43 @@ namespace Emby.Dlna.PlayTo
|
||||
return (true, uTrack);
|
||||
}
|
||||
|
||||
private XElement ParseResponse(string xml)
|
||||
{
|
||||
// Handle different variations sent back by devices
|
||||
try
|
||||
{
|
||||
return XElement.Parse(xml);
|
||||
}
|
||||
catch (XmlException)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// first try to add a root node with a dlna namesapce
|
||||
try
|
||||
{
|
||||
return XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + xml + "</data>")
|
||||
.Descendants()
|
||||
.First();
|
||||
}
|
||||
catch (XmlException)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// some devices send back invalid xml
|
||||
try
|
||||
{
|
||||
return XElement.Parse(xml.Replace("&", "&"));
|
||||
}
|
||||
catch (XmlException)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static uBaseObject CreateUBaseObject(XElement container, string trackUri)
|
||||
{
|
||||
if (container == null)
|
||||
@@ -1093,6 +1126,11 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
private void OnPlaybackStart(uBaseObject mediaInfo)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mediaInfo.Url))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PlaybackStart?.Invoke(this, new PlaybackStartEventArgs
|
||||
{
|
||||
MediaInfo = mediaInfo
|
||||
@@ -1101,8 +1139,7 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
private void OnPlaybackProgress(uBaseObject mediaInfo)
|
||||
{
|
||||
var mediaUrl = mediaInfo.Url;
|
||||
if (string.IsNullOrWhiteSpace(mediaUrl))
|
||||
if (string.IsNullOrWhiteSpace(mediaInfo.Url))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1115,7 +1152,6 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
private void OnPlaybackStop(uBaseObject mediaInfo)
|
||||
{
|
||||
|
||||
PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs
|
||||
{
|
||||
MediaInfo = mediaInfo
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.Didl;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -17,8 +18,8 @@ using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Services;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
@@ -101,9 +102,10 @@ namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
_sessionManager.ReportSessionEnded(_session.Id);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Could throw if the session is already gone
|
||||
_logger.LogError(ex, "Error reporting the end of session {Id}", _session.Id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,20 +113,14 @@ namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
var info = e.Argument;
|
||||
|
||||
info.Headers.TryGetValue("NTS", out string nts);
|
||||
|
||||
if (!info.Headers.TryGetValue("USN", out string usn)) usn = string.Empty;
|
||||
|
||||
if (!info.Headers.TryGetValue("NT", out string nt)) nt = string.Empty;
|
||||
|
||||
if (usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 &&
|
||||
!_disposed)
|
||||
if (!_disposed
|
||||
&& info.Headers.TryGetValue("USN", out string usn)
|
||||
&& usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1
|
||||
&& (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1
|
||||
|| (info.Headers.TryGetValue("NT", out string nt)
|
||||
&& nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1)))
|
||||
{
|
||||
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
OnDeviceUnavailable();
|
||||
}
|
||||
OnDeviceUnavailable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -611,22 +607,34 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
_device.PlaybackStart -= _device_PlaybackStart;
|
||||
_device.PlaybackProgress -= _device_PlaybackProgress;
|
||||
_device.PlaybackStopped -= _device_PlaybackStopped;
|
||||
_device.MediaChanged -= _device_MediaChanged;
|
||||
//_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
|
||||
_device.OnDeviceUnavailable = null;
|
||||
|
||||
_device.Dispose();
|
||||
}
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_device.Dispose();
|
||||
}
|
||||
|
||||
_device.PlaybackStart -= _device_PlaybackStart;
|
||||
_device.PlaybackProgress -= _device_PlaybackProgress;
|
||||
_device.PlaybackStopped -= _device_PlaybackStopped;
|
||||
_device.MediaChanged -= _device_MediaChanged;
|
||||
_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
|
||||
_device.OnDeviceUnavailable = null;
|
||||
_device = null;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
|
||||
|
||||
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -847,13 +855,13 @@ namespace Emby.Dlna.PlayTo
|
||||
if (index == -1) return request;
|
||||
|
||||
var query = url.Substring(index + 1);
|
||||
QueryParamCollection values = MyHttpUtility.ParseQueryString(query);
|
||||
Dictionary<string, string> values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString());
|
||||
|
||||
request.DeviceProfileId = values.Get("DeviceProfileId");
|
||||
request.DeviceId = values.Get("DeviceId");
|
||||
request.MediaSourceId = values.Get("MediaSourceId");
|
||||
request.LiveStreamId = values.Get("LiveStreamId");
|
||||
request.IsDirectStream = string.Equals("true", values.Get("Static"), StringComparison.OrdinalIgnoreCase);
|
||||
request.DeviceProfileId = values.GetValueOrDefault("DeviceProfileId");
|
||||
request.DeviceId = values.GetValueOrDefault("DeviceId");
|
||||
request.MediaSourceId = values.GetValueOrDefault("MediaSourceId");
|
||||
request.LiveStreamId = values.GetValueOrDefault("LiveStreamId");
|
||||
request.IsDirectStream = string.Equals("true", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex");
|
||||
request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex");
|
||||
@@ -867,9 +875,9 @@ namespace Emby.Dlna.PlayTo
|
||||
}
|
||||
}
|
||||
|
||||
private static int? GetIntValue(QueryParamCollection values, string name)
|
||||
private static int? GetIntValue(IReadOnlyDictionary<string, string> values, string name)
|
||||
{
|
||||
var value = values.Get(name);
|
||||
var value = values.GetValueOrDefault(name);
|
||||
|
||||
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
|
||||
{
|
||||
@@ -879,9 +887,9 @@ namespace Emby.Dlna.PlayTo
|
||||
return null;
|
||||
}
|
||||
|
||||
private static long GetLongValue(QueryParamCollection values, string name)
|
||||
private static long GetLongValue(IReadOnlyDictionary<string, string> values, string name)
|
||||
{
|
||||
var value = values.Get(name);
|
||||
var value = values.GetValueOrDefault(name);
|
||||
|
||||
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
|
||||
{
|
||||
|
||||
@@ -9,8 +9,6 @@ namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
public class PlaylistItemFactory
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
public PlaylistItem Create(Photo item, DeviceProfile profile)
|
||||
{
|
||||
var playlistItem = new PlaylistItem
|
||||
|
||||
@@ -107,12 +107,18 @@ namespace Emby.Dlna.PlayTo
|
||||
foreach (var arg in action.ArgumentList)
|
||||
{
|
||||
if (arg.Direction == "out")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.Name == "InstanceID")
|
||||
{
|
||||
stateString += BuildArgumentXml(arg, "0");
|
||||
}
|
||||
else
|
||||
{
|
||||
stateString += BuildArgumentXml(arg, null);
|
||||
}
|
||||
}
|
||||
|
||||
return string.Format(CommandBase, action.Name, xmlNamespace, stateString);
|
||||
@@ -125,11 +131,18 @@ namespace Emby.Dlna.PlayTo
|
||||
foreach (var arg in action.ArgumentList)
|
||||
{
|
||||
if (arg.Direction == "out")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.Name == "InstanceID")
|
||||
{
|
||||
stateString += BuildArgumentXml(arg, "0");
|
||||
}
|
||||
else
|
||||
{
|
||||
stateString += BuildArgumentXml(arg, value.ToString(), commandParameter);
|
||||
}
|
||||
}
|
||||
|
||||
return string.Format(CommandBase, action.Name, xmlNamesapce, stateString);
|
||||
@@ -142,11 +155,17 @@ namespace Emby.Dlna.PlayTo
|
||||
foreach (var arg in action.ArgumentList)
|
||||
{
|
||||
if (arg.Name == "InstanceID")
|
||||
{
|
||||
stateString += BuildArgumentXml(arg, "0");
|
||||
}
|
||||
else if (dictionary.ContainsKey(arg.Name))
|
||||
{
|
||||
stateString += BuildArgumentXml(arg, dictionary[arg.Name]);
|
||||
}
|
||||
else
|
||||
{
|
||||
stateString += BuildArgumentXml(arg, value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return string.Format(CommandBase, action.Name, xmlNamesapce, stateString);
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
public class TransportStateEventArgs : EventArgs
|
||||
{
|
||||
public TRANSPORTSTATE State { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
public class uParser
|
||||
{
|
||||
public static IList<uBaseObject> ParseBrowseXml(XDocument doc)
|
||||
{
|
||||
if (doc == null)
|
||||
{
|
||||
throw new ArgumentException("doc");
|
||||
}
|
||||
|
||||
var list = new List<uBaseObject>();
|
||||
|
||||
var document = doc.Document;
|
||||
|
||||
if (document == null)
|
||||
return list;
|
||||
|
||||
var item = (from result in document.Descendants("Result") select result).FirstOrDefault();
|
||||
|
||||
if (item == null)
|
||||
return list;
|
||||
|
||||
var uPnpResponse = XElement.Parse((string)item);
|
||||
|
||||
var uObjects = from container in uPnpResponse.Elements(uPnpNamespaces.containers)
|
||||
select new uParserObject { Element = container };
|
||||
|
||||
var uObjects2 = from container in uPnpResponse.Elements(uPnpNamespaces.items)
|
||||
select new uParserObject { Element = container };
|
||||
|
||||
list.AddRange(uObjects.Concat(uObjects2).Select(CreateObjectFromXML).Where(uObject => uObject != null));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static uBaseObject CreateObjectFromXML(uParserObject uItem)
|
||||
{
|
||||
return UpnpContainer.Create(uItem.Element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
public class uParserObject
|
||||
{
|
||||
public XElement Element { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ using System.Resources;
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
||||
[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")]
|
||||
|
||||
@@ -7,7 +7,6 @@ using System.Xml;
|
||||
using Emby.Dlna.Didl;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
using MediaBrowser.Model.Xml;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Dlna.Service
|
||||
@@ -18,13 +17,11 @@ namespace Emby.Dlna.Service
|
||||
|
||||
protected readonly IServerConfigurationManager Config;
|
||||
protected readonly ILogger _logger;
|
||||
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
|
||||
|
||||
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
|
||||
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
|
||||
{
|
||||
Config = config;
|
||||
_logger = logger;
|
||||
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
@@ -61,11 +58,13 @@ namespace Emby.Dlna.Service
|
||||
|
||||
using (var streamReader = new StreamReader(request.InputXml))
|
||||
{
|
||||
var readerSettings = XmlReaderSettingsFactory.Create(false);
|
||||
|
||||
readerSettings.CheckCharacters = false;
|
||||
readerSettings.IgnoreProcessingInstructions = true;
|
||||
readerSettings.IgnoreComments = true;
|
||||
var readerSettings = new XmlReaderSettings()
|
||||
{
|
||||
ValidationType = ValidationType.None,
|
||||
CheckCharacters = false,
|
||||
IgnoreProcessingInstructions = true,
|
||||
IgnoreComments = true
|
||||
};
|
||||
|
||||
using (var reader = XmlReader.Create(streamReader, readerSettings))
|
||||
{
|
||||
|
||||
@@ -180,6 +180,12 @@ namespace Emby.Drawing
|
||||
|
||||
var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false);
|
||||
originalImagePath = supportedImageInfo.path;
|
||||
|
||||
if (!File.Exists(originalImagePath))
|
||||
{
|
||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||
}
|
||||
|
||||
dateModified = supportedImageInfo.dateModified;
|
||||
bool requiresTransparency = TransparentImageTypes.Contains(Path.GetExtension(originalImagePath));
|
||||
|
||||
@@ -265,8 +271,6 @@ namespace Emby.Drawing
|
||||
{
|
||||
// If it fails for whatever reason, return the original image
|
||||
_logger.LogError(ex, "Error encoding image");
|
||||
|
||||
// Just spit out the original file if all the options are default
|
||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -8,8 +8,8 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
||||
[assembly: AssemblyProduct("Jellyfin Server")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using MediaBrowser.Model.Diagnostics;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||
|
||||
namespace IsoMounter
|
||||
{
|
||||
@@ -17,9 +18,7 @@ namespace IsoMounter
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private readonly IEnvironmentInfo EnvironmentInfo;
|
||||
private readonly bool ExecutablesAvailable;
|
||||
private readonly IFileSystem FileSystem;
|
||||
private readonly ILogger _logger;
|
||||
private readonly string MountCommand;
|
||||
private readonly string MountPointRoot;
|
||||
@@ -31,11 +30,8 @@ namespace IsoMounter
|
||||
|
||||
#region Constructor(s)
|
||||
|
||||
public LinuxIsoManager(ILogger logger, IFileSystem fileSystem, IEnvironmentInfo environment, IProcessFactory processFactory)
|
||||
public LinuxIsoManager(ILogger logger, IProcessFactory processFactory)
|
||||
{
|
||||
|
||||
EnvironmentInfo = environment;
|
||||
FileSystem = fileSystem;
|
||||
_logger = logger;
|
||||
ProcessFactory = processFactory;
|
||||
|
||||
@@ -111,7 +107,7 @@ namespace IsoMounter
|
||||
public bool CanMount(string path)
|
||||
{
|
||||
|
||||
if (EnvironmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Linux)
|
||||
if (OperatingSystem.Id != OperatingSystemId.Linux)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -120,7 +116,7 @@ namespace IsoMounter
|
||||
Name,
|
||||
path,
|
||||
Path.GetExtension(path),
|
||||
EnvironmentInfo.OperatingSystem,
|
||||
OperatingSystem.Name,
|
||||
ExecutablesAvailable
|
||||
);
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
||||
[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")]
|
||||
|
||||
@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
||||
[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")]
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Emby.Naming.Common;
|
||||
|
||||
namespace Emby.Naming.TV
|
||||
@@ -22,7 +21,9 @@ namespace Emby.Naming.TV
|
||||
// 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)
|
||||
{
|
||||
path += ".mp4";
|
||||
}
|
||||
|
||||
EpisodePathParserResult result = null;
|
||||
|
||||
@@ -35,6 +36,7 @@ namespace Emby.Naming.TV
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (isNamed.HasValue)
|
||||
{
|
||||
if (expression.IsNamed != isNamed.Value)
|
||||
@@ -42,6 +44,7 @@ namespace Emby.Naming.TV
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (isOptimistic.HasValue)
|
||||
{
|
||||
if (expression.IsOptimistic != isOptimistic.Value)
|
||||
@@ -191,13 +194,20 @@ namespace Emby.Naming.TV
|
||||
|
||||
private void FillAdditional(string path, EpisodePathParserResult info, IEnumerable<EpisodeExpression> expressions)
|
||||
{
|
||||
var results = expressions
|
||||
.Where(i => i.IsNamed)
|
||||
.Select(i => Parse(path, i))
|
||||
.Where(i => i.Success);
|
||||
|
||||
foreach (var result in results)
|
||||
foreach (var i in expressions)
|
||||
{
|
||||
if (!i.IsNamed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = Parse(path, i);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(info.SeriesName))
|
||||
{
|
||||
info.SeriesName = result.SeriesName;
|
||||
@@ -208,12 +218,10 @@ namespace Emby.Naming.TV
|
||||
info.EndingEpsiodeNumber = result.EndingEpsiodeNumber;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(info.SeriesName))
|
||||
if (!string.IsNullOrEmpty(info.SeriesName)
|
||||
&& (!info.EpisodeNumber.HasValue || info.EndingEpsiodeNumber.HasValue))
|
||||
{
|
||||
if (!info.EpisodeNumber.HasValue || info.EndingEpsiodeNumber.HasValue)
|
||||
{
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,101 +11,81 @@ namespace Emby.Notifications
|
||||
public class CoreNotificationTypes : INotificationTypeFactory
|
||||
{
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
|
||||
public CoreNotificationTypes(ILocalizationManager localization, IServerApplicationHost appHost)
|
||||
public CoreNotificationTypes(ILocalizationManager localization)
|
||||
{
|
||||
_localization = localization;
|
||||
_appHost = appHost;
|
||||
}
|
||||
|
||||
public IEnumerable<NotificationTypeInfo> GetNotificationTypes()
|
||||
{
|
||||
var knownTypes = new List<NotificationTypeInfo>
|
||||
var knownTypes = new NotificationTypeInfo[]
|
||||
{
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.ApplicationUpdateInstalled.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.InstallationFailed.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.PluginInstalled.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.PluginError.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.PluginUninstalled.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.PluginUpdateInstalled.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.ServerRestartRequired.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.TaskFailed.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.NewLibraryContent.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.AudioPlayback.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.VideoPlayback.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.AudioPlaybackStopped.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.VideoPlaybackStopped.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.CameraImageUploaded.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.UserLockedOut.ToString()
|
||||
}
|
||||
};
|
||||
|
||||
if (!_appHost.CanSelfUpdate)
|
||||
{
|
||||
knownTypes.Add(new NotificationTypeInfo
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.ApplicationUpdateAvailable.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var type in knownTypes)
|
||||
{
|
||||
|
||||
@@ -5,21 +5,17 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Notifications;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Notifications;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Notifications
|
||||
@@ -29,43 +25,40 @@ namespace Emby.Notifications
|
||||
/// </summary>
|
||||
public class Notifications : IServerEntryPoint
|
||||
{
|
||||
private readonly IInstallationManager _installationManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly ITaskManager _taskManager;
|
||||
private readonly INotificationManager _notificationManager;
|
||||
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
|
||||
private Timer LibraryUpdateTimer { get; set; }
|
||||
private readonly object _libraryChangedSyncLock = new object();
|
||||
|
||||
private readonly IConfigurationManager _config;
|
||||
private readonly IDeviceManager _deviceManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IActivityManager _activityManager;
|
||||
|
||||
private string[] _coreNotificationTypes;
|
||||
|
||||
public Notifications(IInstallationManager installationManager, IActivityManager activityManager, ILocalizationManager localization, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost, IConfigurationManager config, IDeviceManager deviceManager)
|
||||
public Notifications(
|
||||
IActivityManager activityManager,
|
||||
ILocalizationManager localization,
|
||||
ILogger logger,
|
||||
INotificationManager notificationManager,
|
||||
ILibraryManager libraryManager,
|
||||
IServerApplicationHost appHost,
|
||||
IConfigurationManager config)
|
||||
{
|
||||
_installationManager = installationManager;
|
||||
_userManager = userManager;
|
||||
_logger = logger;
|
||||
_taskManager = taskManager;
|
||||
_notificationManager = notificationManager;
|
||||
_libraryManager = libraryManager;
|
||||
_sessionManager = sessionManager;
|
||||
_appHost = appHost;
|
||||
_config = config;
|
||||
_deviceManager = deviceManager;
|
||||
_localization = localization;
|
||||
_activityManager = activityManager;
|
||||
|
||||
_coreNotificationTypes = new CoreNotificationTypes(localization, appHost).GetNotificationTypes().Select(i => i.Type).ToArray();
|
||||
_coreNotificationTypes = new CoreNotificationTypes(localization).GetNotificationTypes().Select(i => i.Type).ToArray();
|
||||
}
|
||||
|
||||
public Task RunAsync()
|
||||
@@ -124,10 +117,9 @@ namespace Emby.Notifications
|
||||
return _config.GetConfiguration<NotificationOptions>("notifications");
|
||||
}
|
||||
|
||||
async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e)
|
||||
private async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e)
|
||||
{
|
||||
// This notification is for users who can't auto-update (aka running as service)
|
||||
if (!_appHost.HasUpdateAvailable || _appHost.CanSelfUpdate)
|
||||
if (!_appHost.HasUpdateAvailable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -145,7 +137,7 @@ namespace Emby.Notifications
|
||||
}
|
||||
|
||||
private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
|
||||
void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
|
||||
private void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
|
||||
{
|
||||
if (!FilterItem(e.Item))
|
||||
{
|
||||
|
||||
@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
||||
[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")]
|
||||
|
||||
@@ -9,7 +9,6 @@ using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TagLib;
|
||||
using TagLib.IFD;
|
||||
@@ -21,13 +20,11 @@ namespace Emby.Photos
|
||||
public class PhotoProvider : ICustomMetadataProvider<Photo>, IForcedProvider, IHasItemChangeMonitor
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private IImageProcessor _imageProcessor;
|
||||
|
||||
public PhotoProvider(ILogger logger, IFileSystem fileSystem, IImageProcessor imageProcessor)
|
||||
public PhotoProvider(ILogger logger, IImageProcessor imageProcessor)
|
||||
{
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_imageProcessor = imageProcessor;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
||||
[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")]
|
||||
|
||||
@@ -30,13 +30,10 @@ namespace Emby.Server.Implementations.Activity
|
||||
public class ActivityLogEntryPoint : IServerEntryPoint
|
||||
{
|
||||
private readonly IInstallationManager _installationManager;
|
||||
|
||||
//private readonly ILogger _logger;
|
||||
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;
|
||||
@@ -61,41 +58,37 @@ namespace Emby.Server.Implementations.Activity
|
||||
|
||||
public Task RunAsync()
|
||||
{
|
||||
_taskManager.TaskCompleted += _taskManager_TaskCompleted;
|
||||
_taskManager.TaskCompleted += OnTaskCompleted;
|
||||
|
||||
_installationManager.PluginInstalled += _installationManager_PluginInstalled;
|
||||
_installationManager.PluginUninstalled += _installationManager_PluginUninstalled;
|
||||
_installationManager.PluginUpdated += _installationManager_PluginUpdated;
|
||||
_installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed;
|
||||
_installationManager.PluginInstalled += OnPluginInstalled;
|
||||
_installationManager.PluginUninstalled += OnPluginUninstalled;
|
||||
_installationManager.PluginUpdated += OnPluginUpdated;
|
||||
_installationManager.PackageInstallationFailed += OnPackageInstallationFailed;
|
||||
|
||||
_sessionManager.SessionStarted += _sessionManager_SessionStarted;
|
||||
_sessionManager.AuthenticationFailed += _sessionManager_AuthenticationFailed;
|
||||
_sessionManager.AuthenticationSucceeded += _sessionManager_AuthenticationSucceeded;
|
||||
_sessionManager.SessionEnded += _sessionManager_SessionEnded;
|
||||
_sessionManager.SessionStarted += OnSessionStarted;
|
||||
_sessionManager.AuthenticationFailed += OnAuthenticationFailed;
|
||||
_sessionManager.AuthenticationSucceeded += OnAuthenticationSucceeded;
|
||||
_sessionManager.SessionEnded += OnSessionEnded;
|
||||
|
||||
_sessionManager.PlaybackStart += _sessionManager_PlaybackStart;
|
||||
_sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped;
|
||||
_sessionManager.PlaybackStart += OnPlaybackStart;
|
||||
_sessionManager.PlaybackStopped += OnPlaybackStopped;
|
||||
|
||||
//_subManager.SubtitlesDownloaded += _subManager_SubtitlesDownloaded;
|
||||
_subManager.SubtitleDownloadFailure += _subManager_SubtitleDownloadFailure;
|
||||
_subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;
|
||||
|
||||
_userManager.UserCreated += _userManager_UserCreated;
|
||||
_userManager.UserPasswordChanged += _userManager_UserPasswordChanged;
|
||||
_userManager.UserDeleted += _userManager_UserDeleted;
|
||||
_userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated;
|
||||
_userManager.UserLockedOut += _userManager_UserLockedOut;
|
||||
_userManager.UserCreated += OnUserCreated;
|
||||
_userManager.UserPasswordChanged += OnUserPasswordChanged;
|
||||
_userManager.UserDeleted += OnUserDeleted;
|
||||
_userManager.UserPolicyUpdated += OnUserPolicyUpdated;
|
||||
_userManager.UserLockedOut += OnUserLockedOut;
|
||||
|
||||
//_config.ConfigurationUpdated += _config_ConfigurationUpdated;
|
||||
//_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
|
||||
_deviceManager.CameraImageUploaded += OnCameraImageUploaded;
|
||||
|
||||
_deviceManager.CameraImageUploaded += _deviceManager_CameraImageUploaded;
|
||||
|
||||
_appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
|
||||
_appHost.ApplicationUpdated += OnApplicationUpdated;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
|
||||
private void OnCameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -104,7 +97,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e)
|
||||
private void OnUserLockedOut(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -114,7 +107,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
||||
private void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -125,7 +118,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e)
|
||||
private void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
|
||||
{
|
||||
var item = e.MediaInfo;
|
||||
|
||||
@@ -146,7 +139,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
return;
|
||||
}
|
||||
|
||||
var user = e.Users.First();
|
||||
var user = e.Users[0];
|
||||
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -156,7 +149,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
|
||||
private void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
|
||||
{
|
||||
var item = e.MediaInfo;
|
||||
|
||||
@@ -232,7 +225,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
return null;
|
||||
}
|
||||
|
||||
void _sessionManager_SessionEnded(object sender, SessionEventArgs e)
|
||||
private void OnSessionEnded(object sender, SessionEventArgs e)
|
||||
{
|
||||
string name;
|
||||
var session = e.SessionInfo;
|
||||
@@ -258,7 +251,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
|
||||
private void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
|
||||
{
|
||||
var user = e.Argument.User;
|
||||
|
||||
@@ -271,7 +264,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
|
||||
private void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -282,7 +275,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _appHost_ApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e)
|
||||
private void OnApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -292,25 +285,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("MessageNamedServerConfigurationUpdatedWithValue"), e.Key),
|
||||
Type = "NamedConfigurationUpdated"
|
||||
});
|
||||
}
|
||||
|
||||
void _config_ConfigurationUpdated(object sender, EventArgs e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = _localization.GetLocalizedString("MessageServerConfigurationUpdated"),
|
||||
Type = "ServerConfigurationUpdated"
|
||||
});
|
||||
}
|
||||
|
||||
void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
||||
private void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -320,7 +295,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _userManager_UserDeleted(object sender, GenericEventArgs<User> e)
|
||||
private void OnUserDeleted(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -329,7 +304,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _userManager_UserPasswordChanged(object sender, GenericEventArgs<User> e)
|
||||
private void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -339,7 +314,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _userManager_UserCreated(object sender, GenericEventArgs<User> e)
|
||||
private void OnUserCreated(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -349,18 +324,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("SubtitlesDownloadedForItem"), Notifications.Notifications.GetItemName(e.Item)),
|
||||
Type = "SubtitlesDownloaded",
|
||||
ItemId = e.Item.Id.ToString("N"),
|
||||
ShortOverview = string.Format(_localization.GetLocalizedString("ProviderValue"), e.Provider)
|
||||
});
|
||||
}
|
||||
|
||||
void _sessionManager_SessionStarted(object sender, SessionEventArgs e)
|
||||
private void OnSessionStarted(object sender, SessionEventArgs e)
|
||||
{
|
||||
string name;
|
||||
var session = e.SessionInfo;
|
||||
@@ -386,7 +350,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _installationManager_PluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e)
|
||||
private void OnPluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -397,7 +361,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _installationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
|
||||
private void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -406,7 +370,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _installationManager_PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
|
||||
private void OnPluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -416,7 +380,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
||||
private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
||||
{
|
||||
var installationInfo = e.InstallationInfo;
|
||||
|
||||
@@ -429,7 +393,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e)
|
||||
private void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
|
||||
{
|
||||
var result = e.Result;
|
||||
var task = e.Task;
|
||||
@@ -468,48 +432,36 @@ namespace Emby.Server.Implementations.Activity
|
||||
}
|
||||
|
||||
private void CreateLogEntry(ActivityLogEntry entry)
|
||||
{
|
||||
try
|
||||
{
|
||||
_activityManager.Create(entry);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Logged at lower levels
|
||||
}
|
||||
}
|
||||
=> _activityManager.Create(entry);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_taskManager.TaskCompleted -= _taskManager_TaskCompleted;
|
||||
_taskManager.TaskCompleted -= OnTaskCompleted;
|
||||
|
||||
_installationManager.PluginInstalled -= _installationManager_PluginInstalled;
|
||||
_installationManager.PluginUninstalled -= _installationManager_PluginUninstalled;
|
||||
_installationManager.PluginUpdated -= _installationManager_PluginUpdated;
|
||||
_installationManager.PackageInstallationFailed -= _installationManager_PackageInstallationFailed;
|
||||
_installationManager.PluginInstalled -= OnPluginInstalled;
|
||||
_installationManager.PluginUninstalled -= OnPluginUninstalled;
|
||||
_installationManager.PluginUpdated -= OnPluginUpdated;
|
||||
_installationManager.PackageInstallationFailed -= OnPackageInstallationFailed;
|
||||
|
||||
_sessionManager.SessionStarted -= _sessionManager_SessionStarted;
|
||||
_sessionManager.AuthenticationFailed -= _sessionManager_AuthenticationFailed;
|
||||
_sessionManager.AuthenticationSucceeded -= _sessionManager_AuthenticationSucceeded;
|
||||
_sessionManager.SessionEnded -= _sessionManager_SessionEnded;
|
||||
_sessionManager.SessionStarted -= OnSessionStarted;
|
||||
_sessionManager.AuthenticationFailed -= OnAuthenticationFailed;
|
||||
_sessionManager.AuthenticationSucceeded -= OnAuthenticationSucceeded;
|
||||
_sessionManager.SessionEnded -= OnSessionEnded;
|
||||
|
||||
_sessionManager.PlaybackStart -= _sessionManager_PlaybackStart;
|
||||
_sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped;
|
||||
_sessionManager.PlaybackStart -= OnPlaybackStart;
|
||||
_sessionManager.PlaybackStopped -= OnPlaybackStopped;
|
||||
|
||||
_subManager.SubtitleDownloadFailure -= _subManager_SubtitleDownloadFailure;
|
||||
_subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;
|
||||
|
||||
_userManager.UserCreated -= _userManager_UserCreated;
|
||||
_userManager.UserPasswordChanged -= _userManager_UserPasswordChanged;
|
||||
_userManager.UserDeleted -= _userManager_UserDeleted;
|
||||
_userManager.UserPolicyUpdated -= _userManager_UserPolicyUpdated;
|
||||
_userManager.UserLockedOut -= _userManager_UserLockedOut;
|
||||
_userManager.UserCreated -= OnUserCreated;
|
||||
_userManager.UserPasswordChanged -= OnUserPasswordChanged;
|
||||
_userManager.UserDeleted -= OnUserDeleted;
|
||||
_userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
|
||||
_userManager.UserLockedOut -= OnUserLockedOut;
|
||||
|
||||
_config.ConfigurationUpdated -= _config_ConfigurationUpdated;
|
||||
_config.NamedConfigurationUpdated -= _config_NamedConfigurationUpdated;
|
||||
_deviceManager.CameraImageUploaded -= OnCameraImageUploaded;
|
||||
|
||||
_deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded;
|
||||
|
||||
_appHost.ApplicationUpdated -= _appHost_ApplicationUpdated;
|
||||
_appHost.ApplicationUpdated -= OnApplicationUpdated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -531,6 +483,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
values.Add(CreateValueString(years, "year"));
|
||||
days = days % DaysInYear;
|
||||
}
|
||||
|
||||
// Number of months
|
||||
if (days >= DaysInMonth)
|
||||
{
|
||||
@@ -538,25 +491,39 @@ namespace Emby.Server.Implementations.Activity
|
||||
values.Add(CreateValueString(months, "month"));
|
||||
days = days % DaysInMonth;
|
||||
}
|
||||
|
||||
// Number of days
|
||||
if (days >= 1)
|
||||
{
|
||||
values.Add(CreateValueString(days, "day"));
|
||||
}
|
||||
|
||||
// Number of hours
|
||||
if (span.Hours >= 1)
|
||||
{
|
||||
values.Add(CreateValueString(span.Hours, "hour"));
|
||||
}
|
||||
// Number of minutes
|
||||
if (span.Minutes >= 1)
|
||||
{
|
||||
values.Add(CreateValueString(span.Minutes, "minute"));
|
||||
}
|
||||
|
||||
// Number of seconds (include when 0 if no other components included)
|
||||
if (span.Seconds >= 1 || values.Count == 0)
|
||||
{
|
||||
values.Add(CreateValueString(span.Seconds, "second"));
|
||||
}
|
||||
|
||||
// Combine values into string
|
||||
var builder = new StringBuilder();
|
||||
for (int i = 0; i < values.Count; i++)
|
||||
{
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
builder.Append(i == values.Count - 1 ? " and " : ", ");
|
||||
}
|
||||
|
||||
builder.Append(values[i]);
|
||||
}
|
||||
// Return result
|
||||
|
||||
@@ -39,8 +39,13 @@ namespace Emby.Server.Implementations.Activity
|
||||
{
|
||||
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
|
||||
|
||||
foreach (var item in result.Items.Where(i => !i.UserId.Equals(Guid.Empty)))
|
||||
foreach (var item in result.Items)
|
||||
{
|
||||
if (item.UserId == Guid.Empty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var user = _userManager.GetUserById(item.UserId);
|
||||
|
||||
if (user != null)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
||||
@@ -14,50 +15,52 @@ namespace Emby.Server.Implementations.AppBase
|
||||
/// </summary>
|
||||
protected BaseApplicationPaths(
|
||||
string programDataPath,
|
||||
string appFolderPath,
|
||||
string logDirectoryPath = null,
|
||||
string configurationDirectoryPath = null,
|
||||
string cacheDirectoryPath = null)
|
||||
string logDirectoryPath,
|
||||
string configurationDirectoryPath,
|
||||
string cacheDirectoryPath,
|
||||
string webDirectoryPath)
|
||||
{
|
||||
ProgramDataPath = programDataPath;
|
||||
ProgramSystemPath = appFolderPath;
|
||||
LogDirectoryPath = logDirectoryPath;
|
||||
ConfigurationDirectoryPath = configurationDirectoryPath;
|
||||
CachePath = cacheDirectoryPath;
|
||||
WebPath = webDirectoryPath;
|
||||
|
||||
DataPath = Path.Combine(ProgramDataPath, "data");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the program data folder
|
||||
/// </summary>
|
||||
/// <value>The program data path.</value>
|
||||
public string ProgramDataPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the web UI resources folder
|
||||
/// </summary>
|
||||
/// <value>The web UI resources path.</value>
|
||||
public string WebPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the system folder
|
||||
/// </summary>
|
||||
public string ProgramSystemPath { get; private set; }
|
||||
public string ProgramSystemPath { get; } = AppContext.BaseDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// The _data directory
|
||||
/// </summary>
|
||||
private string _dataDirectory;
|
||||
/// <summary>
|
||||
/// Gets the folder path to the data directory
|
||||
/// </summary>
|
||||
/// <value>The data directory.</value>
|
||||
private string _dataPath;
|
||||
public string DataPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_dataDirectory == null)
|
||||
{
|
||||
_dataDirectory = Path.Combine(ProgramDataPath, "data");
|
||||
|
||||
Directory.CreateDirectory(_dataDirectory);
|
||||
}
|
||||
|
||||
return _dataDirectory;
|
||||
}
|
||||
get => _dataPath;
|
||||
private set => _dataPath = Directory.CreateDirectory(value).FullName;
|
||||
}
|
||||
|
||||
private const string _virtualDataPath = "%AppDataPath%";
|
||||
public string VirtualDataPath => _virtualDataPath;
|
||||
/// <summary>
|
||||
/// Gets the magic strings used for virtual path manipulation.
|
||||
/// </summary>
|
||||
public string VirtualDataPath { get; } = "%AppDataPath%";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the image cache path.
|
||||
@@ -77,61 +80,17 @@ namespace Emby.Server.Implementations.AppBase
|
||||
/// <value>The plugin configurations path.</value>
|
||||
public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to where temporary update files will be stored
|
||||
/// </summary>
|
||||
/// <value>The plugin configurations path.</value>
|
||||
public string TempUpdatePath => Path.Combine(ProgramDataPath, "updates");
|
||||
|
||||
/// <summary>
|
||||
/// The _log directory
|
||||
/// </summary>
|
||||
private string _logDirectoryPath;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the log directory
|
||||
/// </summary>
|
||||
/// <value>The log directory path.</value>
|
||||
public string LogDirectoryPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_logDirectoryPath))
|
||||
{
|
||||
_logDirectoryPath = Path.Combine(ProgramDataPath, "logs");
|
||||
|
||||
Directory.CreateDirectory(_logDirectoryPath);
|
||||
}
|
||||
|
||||
return _logDirectoryPath;
|
||||
}
|
||||
set => _logDirectoryPath = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _config directory
|
||||
/// </summary>
|
||||
private string _configurationDirectoryPath;
|
||||
public string LogDirectoryPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the application configuration root directory
|
||||
/// </summary>
|
||||
/// <value>The configuration directory path.</value>
|
||||
public string ConfigurationDirectoryPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_configurationDirectoryPath))
|
||||
{
|
||||
_configurationDirectoryPath = Path.Combine(ProgramDataPath, "config");
|
||||
|
||||
Directory.CreateDirectory(_configurationDirectoryPath);
|
||||
}
|
||||
|
||||
return _configurationDirectoryPath;
|
||||
}
|
||||
set => _configurationDirectoryPath = value;
|
||||
}
|
||||
public string ConfigurationDirectoryPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the system configuration file
|
||||
@@ -139,29 +98,11 @@ namespace Emby.Server.Implementations.AppBase
|
||||
/// <value>The system configuration file path.</value>
|
||||
public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml");
|
||||
|
||||
/// <summary>
|
||||
/// The _cache directory
|
||||
/// </summary>
|
||||
private string _cachePath;
|
||||
/// <summary>
|
||||
/// Gets the folder path to the cache directory
|
||||
/// </summary>
|
||||
/// <value>The cache directory.</value>
|
||||
public string CachePath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_cachePath))
|
||||
{
|
||||
_cachePath = Path.Combine(ProgramDataPath, "cache");
|
||||
|
||||
Directory.CreateDirectory(_cachePath);
|
||||
}
|
||||
|
||||
return _cachePath;
|
||||
}
|
||||
set => _cachePath = value;
|
||||
}
|
||||
public string CachePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder path to the temp directory within the cache folder
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
get
|
||||
{
|
||||
// Lazy load
|
||||
LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer, FileSystem));
|
||||
LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer));
|
||||
return _configuration;
|
||||
}
|
||||
protected set
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
namespace Emby.Server.Implementations.AppBase
|
||||
@@ -18,9 +17,8 @@ namespace Emby.Server.Implementations.AppBase
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="xmlSerializer">The XML serializer.</param>
|
||||
/// <param name="fileSystem">The file system</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
|
||||
public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer)
|
||||
{
|
||||
object configuration;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,11 +14,9 @@ namespace Emby.Server.Implementations.Archiving
|
||||
/// </summary>
|
||||
public class ZipClient : IZipClient
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public ZipClient(IFileSystem fileSystem)
|
||||
public ZipClient()
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -243,8 +243,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
{
|
||||
foreach (var item in returnItems)
|
||||
{
|
||||
var task = RefreshLatestChannelItems(GetChannelProvider(item), CancellationToken.None);
|
||||
Task.WaitAll(task);
|
||||
RefreshLatestChannelItems(GetChannelProvider(item), CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,9 +302,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
}
|
||||
|
||||
numComplete++;
|
||||
double percent = numComplete;
|
||||
percent /= allChannelsList.Count;
|
||||
|
||||
double percent = (double)numComplete / allChannelsList.Count;
|
||||
progress.Report(100 * percent);
|
||||
}
|
||||
|
||||
@@ -658,9 +655,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
|
||||
foreach (var item in result.Items)
|
||||
{
|
||||
var folder = item as Folder;
|
||||
|
||||
if (folder != null)
|
||||
if (item is Folder folder)
|
||||
{
|
||||
await GetChannelItemsInternal(new InternalItemsQuery
|
||||
{
|
||||
|
||||
@@ -35,64 +35,52 @@ namespace Emby.Server.Implementations.Channels
|
||||
public static string GetUserDistinctValue(User user)
|
||||
{
|
||||
var channels = user.Policy.EnabledChannels
|
||||
.OrderBy(i => i)
|
||||
.ToList();
|
||||
.OrderBy(i => i);
|
||||
|
||||
return string.Join("|", channels.ToArray());
|
||||
return string.Join("|", channels);
|
||||
}
|
||||
|
||||
private void CleanDatabase(CancellationToken cancellationToken)
|
||||
{
|
||||
var installedChannelIds = ((ChannelManager)_channelManager).GetInstalledChannelIds();
|
||||
|
||||
var databaseIds = _libraryManager.GetItemIds(new InternalItemsQuery
|
||||
var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Channel).Name }
|
||||
IncludeItemTypes = new[] { typeof(Channel).Name },
|
||||
ExcludeItemIds = installedChannelIds.ToArray()
|
||||
});
|
||||
|
||||
var invalidIds = databaseIds
|
||||
.Except(installedChannelIds)
|
||||
.ToList();
|
||||
|
||||
foreach (var id in invalidIds)
|
||||
foreach (var channel in uninstalledChannels)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
CleanChannel(id, cancellationToken);
|
||||
CleanChannel((Channel)channel, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanChannel(Guid id, CancellationToken cancellationToken)
|
||||
private void CleanChannel(Channel channel, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Cleaning channel {0} from database", id);
|
||||
_logger.LogInformation("Cleaning channel {0} from database", channel.Id);
|
||||
|
||||
// Delete all channel items
|
||||
var allIds = _libraryManager.GetItemIds(new InternalItemsQuery
|
||||
var items = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
ChannelIds = new[] { id }
|
||||
ChannelIds = new[] { channel.Id }
|
||||
});
|
||||
|
||||
foreach (var deleteId in allIds)
|
||||
foreach (var item in items)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
DeleteItem(deleteId);
|
||||
_libraryManager.DeleteItem(item, new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = false
|
||||
|
||||
}, false);
|
||||
}
|
||||
|
||||
// Finally, delete the channel itself
|
||||
DeleteItem(id);
|
||||
}
|
||||
|
||||
private void DeleteItem(Guid id)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_libraryManager.DeleteItem(item, new DeleteOptions
|
||||
_libraryManager.DeleteItem(channel, new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = false
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Channels
|
||||
{
|
||||
class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
|
||||
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
|
||||
{
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
@@ -10,14 +10,18 @@ using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.Collections
|
||||
{
|
||||
public class CollectionImageProvider : BaseDynamicImageProvider<BoxSet>
|
||||
{
|
||||
public CollectionImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
||||
public CollectionImageProvider(
|
||||
IFileSystem fileSystem,
|
||||
IProviderManager providerManager,
|
||||
IApplicationPaths applicationPaths,
|
||||
IImageProcessor imageProcessor)
|
||||
: base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -32,7 +36,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
return base.Supports(item);
|
||||
}
|
||||
|
||||
protected override List<BaseItem> GetItemsWithImages(BaseItem item)
|
||||
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
|
||||
{
|
||||
var playlist = (BoxSet)item;
|
||||
|
||||
@@ -76,7 +80,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
.ToList();
|
||||
}
|
||||
|
||||
protected override string CreateImage(BaseItem item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
||||
protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
||||
{
|
||||
return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
|
||||
}
|
||||
|
||||
@@ -342,14 +342,12 @@ namespace Emby.Server.Implementations.Collections
|
||||
{
|
||||
private readonly CollectionManager _collectionManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private ILogger _logger;
|
||||
|
||||
public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger)
|
||||
public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, ILogger logger)
|
||||
{
|
||||
_collectionManager = (CollectionManager)collectionManager;
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,23 +74,14 @@ 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>
|
||||
|
||||
13
Emby.Server.Implementations/ConfigurationOptions.cs
Normal file
13
Emby.Server.Implementations/ConfigurationOptions.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Emby.Server.Implementations
|
||||
{
|
||||
public static class ConfigurationOptions
|
||||
{
|
||||
public static readonly Dictionary<string, string> Configuration = new Dictionary<string, string>
|
||||
{
|
||||
{"HttpListenerHost:DefaultRedirectPath", "web/index.html"},
|
||||
{"MusicBrainz:BaseUrl", "https://www.musicbrainz.org"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
@@ -8,6 +10,39 @@ namespace Emby.Server.Implementations.Cryptography
|
||||
{
|
||||
public class CryptographyProvider : ICryptoProvider
|
||||
{
|
||||
private static readonly HashSet<string> _supportedHashMethods = new HashSet<string>()
|
||||
{
|
||||
"MD5",
|
||||
"System.Security.Cryptography.MD5",
|
||||
"SHA",
|
||||
"SHA1",
|
||||
"System.Security.Cryptography.SHA1",
|
||||
"SHA256",
|
||||
"SHA-256",
|
||||
"System.Security.Cryptography.SHA256",
|
||||
"SHA384",
|
||||
"SHA-384",
|
||||
"System.Security.Cryptography.SHA384",
|
||||
"SHA512",
|
||||
"SHA-512",
|
||||
"System.Security.Cryptography.SHA512"
|
||||
};
|
||||
|
||||
public string DefaultHashMethod => "PBKDF2";
|
||||
|
||||
private RandomNumberGenerator _randomNumberGenerator;
|
||||
|
||||
private const int _defaultIterations = 1000;
|
||||
|
||||
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
|
||||
_randomNumberGenerator = RandomNumberGenerator.Create();
|
||||
}
|
||||
|
||||
public Guid GetMD5(string str)
|
||||
{
|
||||
return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str)));
|
||||
@@ -36,5 +71,98 @@ namespace Emby.Server.Implementations.Cryptography
|
||||
return provider.ComputeHash(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetSupportedHashMethods()
|
||||
{
|
||||
return _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
|
||||
if (method == DefaultHashMethod)
|
||||
{
|
||||
using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations))
|
||||
{
|
||||
return r.GetBytes(32);
|
||||
}
|
||||
}
|
||||
|
||||
throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}");
|
||||
}
|
||||
|
||||
public byte[] ComputeHash(string hashMethod, byte[] bytes)
|
||||
{
|
||||
return ComputeHash(hashMethod, bytes, Array.Empty<byte>());
|
||||
}
|
||||
|
||||
public byte[] ComputeHashWithDefaultMethod(byte[] bytes)
|
||||
{
|
||||
return ComputeHash(DefaultHashMethod, bytes);
|
||||
}
|
||||
|
||||
public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt)
|
||||
{
|
||||
if (hashMethod == DefaultHashMethod)
|
||||
{
|
||||
return PBKDF2(hashMethod, bytes, salt, _defaultIterations);
|
||||
}
|
||||
else if (_supportedHashMethods.Contains(hashMethod))
|
||||
{
|
||||
using (var h = HashAlgorithm.Create(hashMethod))
|
||||
{
|
||||
if (salt.Length == 0)
|
||||
{
|
||||
return h.ComputeHash(bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] salted = new byte[bytes.Length + salt.Length];
|
||||
Array.Copy(bytes, salted, bytes.Length);
|
||||
Array.Copy(salt, 0, salted, bytes.Length, salt.Length);
|
||||
return h.ComputeHash(salted);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public byte[] GenerateSalt()
|
||||
{
|
||||
byte[] salt = new byte[64];
|
||||
_randomNumberGenerator.GetBytes(salt);
|
||||
return salt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ namespace Emby.Server.Implementations.Data
|
||||
});
|
||||
}
|
||||
|
||||
db.ExecuteAll(string.Join(";", queries.ToArray()));
|
||||
db.ExecuteAll(string.Join(";", queries));
|
||||
Logger.LogInformation("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First());
|
||||
}
|
||||
|
||||
@@ -232,23 +232,6 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
protected virtual int? CacheSize => null;
|
||||
|
||||
internal static void CheckOk(int rc)
|
||||
{
|
||||
string msg = "";
|
||||
|
||||
if (raw.SQLITE_OK != rc)
|
||||
{
|
||||
throw CreateException((ErrorCode)rc, msg);
|
||||
}
|
||||
}
|
||||
|
||||
internal static Exception CreateException(ErrorCode rc, string msg)
|
||||
{
|
||||
var exp = new Exception(msg);
|
||||
|
||||
return exp;
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
protected void CheckDisposed()
|
||||
{
|
||||
@@ -375,13 +358,6 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
}
|
||||
|
||||
public class DummyToken : IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static IDisposable Read(this ReaderWriterLockSlim obj)
|
||||
{
|
||||
//if (BaseSqliteRepository.ThreadSafeMode > 0)
|
||||
@@ -390,6 +366,7 @@ namespace Emby.Server.Implementations.Data
|
||||
//}
|
||||
return new WriteLockToken(obj);
|
||||
}
|
||||
|
||||
public static IDisposable Write(this ReaderWriterLockSlim obj)
|
||||
{
|
||||
//if (BaseSqliteRepository.ThreadSafeMode > 0)
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Data
|
||||
@@ -13,18 +10,12 @@ namespace Emby.Server.Implementations.Data
|
||||
public class CleanDatabaseScheduledTask : ILibraryPostScanTask
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
|
||||
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths)
|
||||
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger logger)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_itemRepo = itemRepo;
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_appPaths = appPaths;
|
||||
}
|
||||
|
||||
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
|
||||
@@ -90,9 +90,10 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
throw new ArgumentNullException(nameof(displayPreferences));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(displayPreferences.Id))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(displayPreferences.Id));
|
||||
throw new ArgumentException("Display preferences has an invalid Id", nameof(displayPreferences));
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -22,9 +22,9 @@ using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Reflection;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
@@ -56,6 +56,8 @@ namespace Emby.Server.Implementations.Data
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private IServerApplicationHost _appHost;
|
||||
|
||||
private readonly ILocalizationManager _localization;
|
||||
|
||||
public IImageProcessor ImageProcessor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -66,7 +68,7 @@ namespace Emby.Server.Implementations.Data
|
||||
IServerApplicationHost appHost,
|
||||
IJsonSerializer jsonSerializer,
|
||||
ILoggerFactory loggerFactory,
|
||||
IAssemblyInfo assemblyInfo)
|
||||
ILocalizationManager localization)
|
||||
: base(loggerFactory.CreateLogger(nameof(SqliteItemRepository)))
|
||||
{
|
||||
if (config == null)
|
||||
@@ -82,7 +84,8 @@ namespace Emby.Server.Implementations.Data
|
||||
_appHost = appHost;
|
||||
_config = config;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_typeMapper = new TypeMapper(assemblyInfo);
|
||||
_typeMapper = new TypeMapper();
|
||||
_localization = localization;
|
||||
|
||||
DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
|
||||
}
|
||||
@@ -536,7 +539,7 @@ namespace Emby.Server.Implementations.Data
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
SaveItems(new List<BaseItem> { item }, cancellationToken);
|
||||
SaveItems(new [] { item }, cancellationToken);
|
||||
}
|
||||
|
||||
public void SaveImages(BaseItem item)
|
||||
@@ -576,7 +579,7 @@ namespace Emby.Server.Implementations.Data
|
||||
/// or
|
||||
/// cancellationToken
|
||||
/// </exception>
|
||||
public void SaveItems(List<BaseItem> items, CancellationToken cancellationToken)
|
||||
public void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken)
|
||||
{
|
||||
if (items == null)
|
||||
{
|
||||
@@ -587,7 +590,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
CheckDisposed();
|
||||
|
||||
var tuples = new List<Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>>();
|
||||
var tuples = new List<(BaseItem, List<Guid>, BaseItem, string, List<string>)>();
|
||||
foreach (var item in items)
|
||||
{
|
||||
var ancestorIds = item.SupportsAncestors ?
|
||||
@@ -599,7 +602,7 @@ namespace Emby.Server.Implementations.Data
|
||||
var userdataKey = item.GetUserDataKeys().FirstOrDefault();
|
||||
var inheritedTags = item.GetInheritedTags();
|
||||
|
||||
tuples.Add(new Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>(item, ancestorIds, topParent, userdataKey, inheritedTags));
|
||||
tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags));
|
||||
}
|
||||
|
||||
using (WriteLock.Write())
|
||||
@@ -615,7 +618,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveItemsInTranscation(IDatabaseConnection db, List<Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>> tuples)
|
||||
private void SaveItemsInTranscation(IDatabaseConnection db, IEnumerable<(BaseItem, List<Guid>, BaseItem, string, List<string>)> tuples)
|
||||
{
|
||||
var statements = PrepareAllSafe(db, new string[]
|
||||
{
|
||||
@@ -966,7 +969,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
if (item.ExtraIds.Length > 0)
|
||||
{
|
||||
saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds.ToArray()));
|
||||
saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1183,9 +1186,9 @@ namespace Emby.Server.Implementations.Data
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public BaseItem RetrieveItem(Guid id)
|
||||
{
|
||||
if (id.Equals(Guid.Empty))
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
throw new ArgumentException(nameof(id), "Guid can't be empty");
|
||||
}
|
||||
|
||||
CheckDisposed();
|
||||
@@ -2079,14 +2082,14 @@ namespace Emby.Server.Implementations.Data
|
||||
return false;
|
||||
}
|
||||
|
||||
var sortingFields = query.OrderBy.Select(i => i.Item1);
|
||||
var sortingFields = new HashSet<string>(query.OrderBy.Select(i => i.Item1), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase)
|
||||
|| sortingFields.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase)
|
||||
|| sortingFields.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase)
|
||||
|| sortingFields.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase)
|
||||
|| sortingFields.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase)
|
||||
|| sortingFields.Contains(ItemSortBy.SeriesDatePlayed, StringComparer.OrdinalIgnoreCase)
|
||||
return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked)
|
||||
|| sortingFields.Contains(ItemSortBy.IsPlayed)
|
||||
|| sortingFields.Contains(ItemSortBy.IsUnplayed)
|
||||
|| sortingFields.Contains(ItemSortBy.PlayCount)
|
||||
|| sortingFields.Contains(ItemSortBy.DatePlayed)
|
||||
|| sortingFields.Contains(ItemSortBy.SeriesDatePlayed)
|
||||
|| query.IsFavoriteOrLiked.HasValue
|
||||
|| query.IsFavorite.HasValue
|
||||
|| query.IsResumable.HasValue
|
||||
@@ -2094,9 +2097,9 @@ namespace Emby.Server.Implementations.Data
|
||||
|| query.IsLiked.HasValue;
|
||||
}
|
||||
|
||||
private readonly List<ItemFields> allFields = Enum.GetNames(typeof(ItemFields))
|
||||
private readonly ItemFields[] _allFields = Enum.GetNames(typeof(ItemFields))
|
||||
.Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
|
||||
.ToList();
|
||||
.ToArray();
|
||||
|
||||
private string[] GetColumnNamesFromField(ItemFields field)
|
||||
{
|
||||
@@ -2151,18 +2154,26 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> _programExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"Series",
|
||||
"Season",
|
||||
"MusicAlbum",
|
||||
"MusicArtist",
|
||||
"PhotoAlbum"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> _programTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"Program",
|
||||
"TvChannel",
|
||||
"LiveTvProgram",
|
||||
"LiveTvTvChannel"
|
||||
};
|
||||
|
||||
private bool HasProgramAttributes(InternalItemsQuery query)
|
||||
{
|
||||
var excludeParentTypes = new string[]
|
||||
{
|
||||
"Series",
|
||||
"Season",
|
||||
"MusicAlbum",
|
||||
"MusicArtist",
|
||||
"PhotoAlbum"
|
||||
};
|
||||
|
||||
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
if (_programExcludeParentTypes.Contains(query.ParentType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -2172,29 +2183,18 @@ namespace Emby.Server.Implementations.Data
|
||||
return true;
|
||||
}
|
||||
|
||||
var types = new string[]
|
||||
{
|
||||
"Program",
|
||||
"TvChannel",
|
||||
"LiveTvProgram",
|
||||
"LiveTvTvChannel"
|
||||
};
|
||||
|
||||
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
|
||||
return query.IncludeItemTypes.Any(x => _programTypes.Contains(x));
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> _serviceTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"TvChannel",
|
||||
"LiveTvTvChannel"
|
||||
};
|
||||
|
||||
private bool HasServiceName(InternalItemsQuery query)
|
||||
{
|
||||
var excludeParentTypes = new string[]
|
||||
{
|
||||
"Series",
|
||||
"Season",
|
||||
"MusicAlbum",
|
||||
"MusicArtist",
|
||||
"PhotoAlbum"
|
||||
};
|
||||
|
||||
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
if (_programExcludeParentTypes.Contains(query.ParentType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -2204,27 +2204,18 @@ namespace Emby.Server.Implementations.Data
|
||||
return true;
|
||||
}
|
||||
|
||||
var types = new string[]
|
||||
{
|
||||
"TvChannel",
|
||||
"LiveTvTvChannel"
|
||||
};
|
||||
|
||||
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
|
||||
return query.IncludeItemTypes.Any(x => _serviceTypes.Contains(x));
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> _startDateTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"Program",
|
||||
"LiveTvProgram"
|
||||
};
|
||||
|
||||
private bool HasStartDate(InternalItemsQuery query)
|
||||
{
|
||||
var excludeParentTypes = new string[]
|
||||
{
|
||||
"Series",
|
||||
"Season",
|
||||
"MusicAlbum",
|
||||
"MusicArtist",
|
||||
"PhotoAlbum"
|
||||
};
|
||||
|
||||
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
if (_programExcludeParentTypes.Contains(query.ParentType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -2234,13 +2225,7 @@ namespace Emby.Server.Implementations.Data
|
||||
return true;
|
||||
}
|
||||
|
||||
var types = new string[]
|
||||
{
|
||||
"Program",
|
||||
"LiveTvProgram"
|
||||
};
|
||||
|
||||
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
|
||||
return query.IncludeItemTypes.Any(x => _startDateTypes.Contains(x));
|
||||
}
|
||||
|
||||
private bool HasEpisodeAttributes(InternalItemsQuery query)
|
||||
@@ -2263,16 +2248,26 @@ namespace Emby.Server.Implementations.Data
|
||||
return query.IncludeItemTypes.Contains("Trailer", StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
|
||||
private static readonly HashSet<string> _artistExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"Series",
|
||||
"Season",
|
||||
"PhotoAlbum"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> _artistsTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"Audio",
|
||||
"MusicAlbum",
|
||||
"MusicVideo",
|
||||
"AudioBook",
|
||||
"AudioPodcast"
|
||||
};
|
||||
|
||||
private bool HasArtistFields(InternalItemsQuery query)
|
||||
{
|
||||
var excludeParentTypes = new string[]
|
||||
{
|
||||
"Series",
|
||||
"Season",
|
||||
"PhotoAlbum"
|
||||
};
|
||||
|
||||
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
if (_artistExcludeParentTypes.Contains(query.ParentType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -2282,18 +2277,17 @@ namespace Emby.Server.Implementations.Data
|
||||
return true;
|
||||
}
|
||||
|
||||
var types = new string[]
|
||||
{
|
||||
"Audio",
|
||||
"MusicAlbum",
|
||||
"MusicVideo",
|
||||
"AudioBook",
|
||||
"AudioPodcast"
|
||||
};
|
||||
|
||||
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
|
||||
return query.IncludeItemTypes.Any(x => _artistsTypes.Contains(x));
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> _seriesTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"Book",
|
||||
"AudioBook",
|
||||
"Episode",
|
||||
"Season"
|
||||
};
|
||||
|
||||
private bool HasSeriesFields(InternalItemsQuery query)
|
||||
{
|
||||
if (string.Equals(query.ParentType, "PhotoAlbum", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -2306,26 +2300,18 @@ namespace Emby.Server.Implementations.Data
|
||||
return true;
|
||||
}
|
||||
|
||||
var types = new string[]
|
||||
{
|
||||
"Book",
|
||||
"AudioBook",
|
||||
"Episode",
|
||||
"Season"
|
||||
};
|
||||
|
||||
return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
|
||||
return query.IncludeItemTypes.Any(x => _seriesTypes.Contains(x));
|
||||
}
|
||||
|
||||
private string[] GetFinalColumnsToSelect(InternalItemsQuery query, string[] startColumns)
|
||||
private List<string> GetFinalColumnsToSelect(InternalItemsQuery query, IEnumerable<string> startColumns)
|
||||
{
|
||||
var list = startColumns.ToList();
|
||||
|
||||
foreach (var field in allFields)
|
||||
foreach (var field in _allFields)
|
||||
{
|
||||
if (!HasField(query, field))
|
||||
{
|
||||
foreach (var fieldToRemove in GetColumnNamesFromField(field).ToList())
|
||||
foreach (var fieldToRemove in GetColumnNamesFromField(field))
|
||||
{
|
||||
list.Remove(fieldToRemove);
|
||||
}
|
||||
@@ -2419,11 +2405,14 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
list.Add(builder.ToString());
|
||||
|
||||
var excludeIds = query.ExcludeItemIds.ToList();
|
||||
excludeIds.Add(item.Id);
|
||||
excludeIds.AddRange(item.ExtraIds);
|
||||
var oldLen = query.ExcludeItemIds.Length;
|
||||
var newLen = oldLen + item.ExtraIds.Length + 1;
|
||||
var excludeIds = new Guid[newLen];
|
||||
query.ExcludeItemIds.CopyTo(excludeIds, 0);
|
||||
excludeIds[oldLen] = item.Id;
|
||||
item.ExtraIds.CopyTo(excludeIds, oldLen + 1);
|
||||
|
||||
query.ExcludeItemIds = excludeIds.ToArray();
|
||||
query.ExcludeItemIds = excludeIds;
|
||||
query.ExcludeProviderIds = item.ProviderIds;
|
||||
}
|
||||
|
||||
@@ -2444,7 +2433,7 @@ namespace Emby.Server.Implementations.Data
|
||||
list.Add(builder.ToString());
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
return list;
|
||||
}
|
||||
|
||||
private void BindSearchParams(InternalItemsQuery query, IStatement statement)
|
||||
@@ -2723,18 +2712,17 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
private void AddItem(List<BaseItem> items, BaseItem newItem)
|
||||
{
|
||||
var providerIds = newItem.ProviderIds.ToList();
|
||||
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
var item = items[i];
|
||||
|
||||
foreach (var providerId in providerIds)
|
||||
foreach (var providerId in newItem.ProviderIds)
|
||||
{
|
||||
if (providerId.Key == MetadataProviders.TmdbCollection.ToString())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.GetProviderId(providerId.Key) == providerId.Value)
|
||||
{
|
||||
if (newItem.SourceType == SourceType.Library)
|
||||
@@ -2753,15 +2741,15 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds;
|
||||
|
||||
int slowThreshold = 1000;
|
||||
int slowThreshold = 100;
|
||||
|
||||
#if DEBUG
|
||||
slowThreshold = 250;
|
||||
slowThreshold = 10;
|
||||
#endif
|
||||
|
||||
if (elapsed >= slowThreshold)
|
||||
{
|
||||
Logger.LogWarning("{0} query time (slow): {1}ms. Query: {2}",
|
||||
Logger.LogWarning("{0} query time (slow): {1:g}. Query: {2}",
|
||||
methodName,
|
||||
elapsed,
|
||||
commandText);
|
||||
@@ -2806,7 +2794,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
var whereText = whereClauses.Count == 0 ?
|
||||
string.Empty :
|
||||
" where " + string.Join(" AND ", whereClauses.ToArray());
|
||||
" where " + string.Join(" AND ", whereClauses);
|
||||
|
||||
commandText += whereText
|
||||
+ GetGroupBy(query)
|
||||
@@ -2930,25 +2918,31 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
private string GetOrderByText(InternalItemsQuery query)
|
||||
{
|
||||
var orderBy = query.OrderBy.ToList();
|
||||
var enableOrderInversion = false;
|
||||
|
||||
if (query.SimilarTo != null && orderBy.Count == 0)
|
||||
if (string.IsNullOrEmpty(query.SearchTerm))
|
||||
{
|
||||
orderBy.Add(new ValueTuple<string, SortOrder>("SimilarityScore", SortOrder.Descending));
|
||||
orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending));
|
||||
int oldLen = query.OrderBy.Length;
|
||||
|
||||
if (query.SimilarTo != null && oldLen == 0)
|
||||
{
|
||||
var arr = new (string, SortOrder)[oldLen + 2];
|
||||
query.OrderBy.CopyTo(arr, 0);
|
||||
arr[oldLen] = ("SimilarityScore", SortOrder.Descending);
|
||||
arr[oldLen + 1] = (ItemSortBy.Random, SortOrder.Ascending);
|
||||
query.OrderBy = arr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
query.OrderBy = new []
|
||||
{
|
||||
("SearchScore", SortOrder.Descending),
|
||||
(ItemSortBy.SortName, SortOrder.Ascending)
|
||||
};
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(query.SearchTerm))
|
||||
{
|
||||
orderBy = new List<(string, SortOrder)>();
|
||||
orderBy.Add(new ValueTuple<string, SortOrder>("SearchScore", SortOrder.Descending));
|
||||
orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending));
|
||||
}
|
||||
var orderBy = query.OrderBy;
|
||||
|
||||
query.OrderBy = orderBy.ToArray();
|
||||
|
||||
if (orderBy.Count == 0)
|
||||
if (orderBy.Length == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
@@ -2957,6 +2951,7 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
var columnMap = MapOrderByField(i.Item1, query);
|
||||
var columnAscending = i.Item2 == SortOrder.Ascending;
|
||||
const bool enableOrderInversion = false;
|
||||
if (columnMap.Item2 && enableOrderInversion)
|
||||
{
|
||||
columnAscending = !columnAscending;
|
||||
@@ -2968,7 +2963,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}));
|
||||
}
|
||||
|
||||
private ValueTuple<string, bool> MapOrderByField(string name, InternalItemsQuery query)
|
||||
private (string, bool) MapOrderByField(string name, InternalItemsQuery query)
|
||||
{
|
||||
if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -3218,7 +3213,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
var whereText = whereClauses.Count == 0 ?
|
||||
string.Empty :
|
||||
" where " + string.Join(" AND ", whereClauses.ToArray());
|
||||
" where " + string.Join(" AND ", whereClauses);
|
||||
|
||||
commandText += whereText
|
||||
+ GetGroupBy(query)
|
||||
@@ -4378,7 +4373,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
else if (query.Years.Length > 1)
|
||||
{
|
||||
var val = string.Join(",", query.Years.ToArray());
|
||||
var val = string.Join(",", query.Years);
|
||||
|
||||
whereClauses.Add("ProductionYear in (" + val + ")");
|
||||
}
|
||||
@@ -4952,7 +4947,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
return result;
|
||||
}
|
||||
|
||||
return new[] { value }.Where(IsValidType);
|
||||
if (IsValidType(value))
|
||||
{
|
||||
return new[] { value };
|
||||
}
|
||||
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
public void DeleteItem(Guid id, CancellationToken cancellationToken)
|
||||
@@ -5215,32 +5215,32 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
}
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
|
||||
{
|
||||
return GetItemValues(query, new[] { 0, 1 }, typeof(MusicArtist).FullName);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
|
||||
{
|
||||
return GetItemValues(query, new[] { 0 }, typeof(MusicArtist).FullName);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
|
||||
{
|
||||
return GetItemValues(query, new[] { 1 }, typeof(MusicArtist).FullName);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
|
||||
{
|
||||
return GetItemValues(query, new[] { 3 }, typeof(Studio).FullName);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
|
||||
{
|
||||
return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
|
||||
{
|
||||
return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName);
|
||||
}
|
||||
@@ -5317,7 +5317,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
}
|
||||
}
|
||||
|
||||
private QueryResult<Tuple<BaseItem, ItemCounts>> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType)
|
||||
private QueryResult<(BaseItem, ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
@@ -5335,7 +5335,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
var typeClause = itemValueTypes.Length == 1 ?
|
||||
("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) :
|
||||
("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture)).ToArray()) + ")");
|
||||
("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")");
|
||||
|
||||
InternalItemsQuery typeSubQuery = null;
|
||||
|
||||
@@ -5363,11 +5363,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND " + typeClause + ")");
|
||||
|
||||
var typeWhereText = whereClauses.Count == 0 ?
|
||||
string.Empty :
|
||||
" where " + string.Join(" AND ", whereClauses);
|
||||
|
||||
itemCountColumnQuery += typeWhereText;
|
||||
itemCountColumnQuery += " where " + string.Join(" AND ", whereClauses);
|
||||
|
||||
itemCountColumns = new Dictionary<string, string>()
|
||||
{
|
||||
@@ -5400,7 +5396,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
IsSeries = query.IsSeries
|
||||
};
|
||||
|
||||
columns = GetFinalColumnsToSelect(query, columns.ToArray()).ToList();
|
||||
columns = GetFinalColumnsToSelect(query, columns);
|
||||
|
||||
var commandText = "select "
|
||||
+ string.Join(",", columns)
|
||||
@@ -5492,8 +5488,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
{
|
||||
return connection.RunInTransaction(db =>
|
||||
{
|
||||
var list = new List<Tuple<BaseItem, ItemCounts>>();
|
||||
var result = new QueryResult<Tuple<BaseItem, ItemCounts>>();
|
||||
var list = new List<(BaseItem, ItemCounts)>();
|
||||
var result = new QueryResult<(BaseItem, ItemCounts)>();
|
||||
|
||||
var statements = PrepareAllSafe(db, statementTexts);
|
||||
|
||||
@@ -5531,7 +5527,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
{
|
||||
var countStartColumn = columns.Count - 1;
|
||||
|
||||
list.Add(new Tuple<BaseItem, ItemCounts>(item, GetItemCounts(row, countStartColumn, typesToCount)));
|
||||
list.Add((item, GetItemCounts(row, countStartColumn, typesToCount)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6196,8 +6192,13 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
item.ColorTransfer = reader[34].ToString();
|
||||
}
|
||||
|
||||
if (item.Type == MediaStreamType.Subtitle){
|
||||
item.localizedUndefined = _localization.GetLocalizedString("Undefined");
|
||||
item.localizedDefault = _localization.GetLocalizedString("Default");
|
||||
item.localizedForced = _localization.GetLocalizedString("Forced");
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,9 +119,9 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
list.Add(row[0].ReadGuidFromBlob());
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Logger.LogError(ex, "Error while getting user");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
TryMigrateToLocalUsersTable(connection);
|
||||
}
|
||||
|
||||
RemoveEmptyPasswordHashes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +75,38 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveEmptyPasswordHashes()
|
||||
{
|
||||
foreach (var user in RetrieveAllUsers())
|
||||
{
|
||||
// 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))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
user.Password = null;
|
||||
var serialized = _jsonSerializer.SerializeToBytes(user);
|
||||
|
||||
using (WriteLock.Write())
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a user in the repo
|
||||
/// </summary>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Reflection;
|
||||
|
||||
namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
@@ -10,16 +9,13 @@ namespace Emby.Server.Implementations.Data
|
||||
/// </summary>
|
||||
public class TypeMapper
|
||||
{
|
||||
private readonly IAssemblyInfo _assemblyInfo;
|
||||
|
||||
/// <summary>
|
||||
/// This holds all the types in the running assemblies so that we can de-serialize properly when we don't have strong types
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, Type> _typeMap = new ConcurrentDictionary<string, Type>();
|
||||
|
||||
public TypeMapper(IAssemblyInfo assemblyInfo)
|
||||
public TypeMapper()
|
||||
{
|
||||
_assemblyInfo = assemblyInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -45,8 +41,7 @@ namespace Emby.Server.Implementations.Data
|
||||
/// <returns>Type.</returns>
|
||||
private Type LookupType(string typeName)
|
||||
{
|
||||
return _assemblyInfo
|
||||
.GetCurrentAssemblies()
|
||||
return AppDomain.CurrentDomain.GetAssemblies()
|
||||
.Select(a => a.GetType(typeName))
|
||||
.FirstOrDefault(t => t != null);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace Emby.Server.Implementations.Devices
|
||||
{
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
private readonly object _syncLock = new object();
|
||||
|
||||
@@ -86,19 +85,10 @@ namespace Emby.Server.Implementations.Devices
|
||||
|
||||
private string _id;
|
||||
|
||||
public DeviceId(
|
||||
IApplicationPaths appPaths,
|
||||
ILoggerFactory loggerFactory,
|
||||
IFileSystem fileSystem)
|
||||
public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (fileSystem == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fileSystem));
|
||||
}
|
||||
|
||||
_appPaths = appPaths;
|
||||
_logger = loggerFactory.CreateLogger("SystemId");
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
public string Value => _id ?? (_id = GetDeviceId());
|
||||
|
||||
@@ -34,8 +34,6 @@ namespace Emby.Server.Implementations.Devices
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILibraryMonitor _libraryMonitor;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ILogger _logger;
|
||||
private readonly INetworkManager _network;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILocalizationManager _localizationManager;
|
||||
|
||||
@@ -55,17 +53,13 @@ namespace Emby.Server.Implementations.Devices
|
||||
IUserManager userManager,
|
||||
IFileSystem fileSystem,
|
||||
ILibraryMonitor libraryMonitor,
|
||||
IServerConfigurationManager config,
|
||||
ILoggerFactory loggerFactory,
|
||||
INetworkManager network)
|
||||
IServerConfigurationManager config)
|
||||
{
|
||||
_json = json;
|
||||
_userManager = userManager;
|
||||
_fileSystem = fileSystem;
|
||||
_libraryMonitor = libraryMonitor;
|
||||
_config = config;
|
||||
_logger = loggerFactory.CreateLogger(nameof(DeviceManager));
|
||||
_network = network;
|
||||
_libraryManager = libraryManager;
|
||||
_localizationManager = localizationManager;
|
||||
_authRepo = authRepo;
|
||||
@@ -414,14 +408,12 @@ namespace Emby.Server.Implementations.Devices
|
||||
{
|
||||
private readonly DeviceManager _deviceManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private ILogger _logger;
|
||||
|
||||
public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger)
|
||||
public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, ILogger logger)
|
||||
{
|
||||
_deviceManager = (DeviceManager)deviceManager;
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@ namespace Emby.Server.Implementations.Diagnostics
|
||||
{
|
||||
public class CommonProcess : IProcess
|
||||
{
|
||||
public event EventHandler Exited;
|
||||
|
||||
private readonly ProcessOptions _options;
|
||||
private readonly Process _process;
|
||||
|
||||
private bool _disposed = false;
|
||||
private bool _hasExited;
|
||||
|
||||
public CommonProcess(ProcessOptions options)
|
||||
{
|
||||
_options = options;
|
||||
StartInfo = options;
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
@@ -27,10 +27,10 @@ namespace Emby.Server.Implementations.Diagnostics
|
||||
CreateNoWindow = options.CreateNoWindow,
|
||||
RedirectStandardError = options.RedirectStandardError,
|
||||
RedirectStandardInput = options.RedirectStandardInput,
|
||||
RedirectStandardOutput = options.RedirectStandardOutput
|
||||
RedirectStandardOutput = options.RedirectStandardOutput,
|
||||
ErrorDialog = options.ErrorDialog
|
||||
};
|
||||
|
||||
startInfo.ErrorDialog = options.ErrorDialog;
|
||||
|
||||
if (options.IsHidden)
|
||||
{
|
||||
@@ -45,11 +45,22 @@ namespace Emby.Server.Implementations.Diagnostics
|
||||
if (options.EnableRaisingEvents)
|
||||
{
|
||||
_process.EnableRaisingEvents = true;
|
||||
_process.Exited += _process_Exited;
|
||||
_process.Exited += OnProcessExited;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _hasExited;
|
||||
public event EventHandler Exited;
|
||||
|
||||
public ProcessOptions StartInfo { get; }
|
||||
|
||||
public StreamWriter StandardInput => _process.StandardInput;
|
||||
|
||||
public StreamReader StandardError => _process.StandardError;
|
||||
|
||||
public StreamReader StandardOutput => _process.StandardOutput;
|
||||
|
||||
public int ExitCode => _process.ExitCode;
|
||||
|
||||
private bool HasExited
|
||||
{
|
||||
get
|
||||
@@ -72,25 +83,6 @@ namespace Emby.Server.Implementations.Diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
private void _process_Exited(object sender, EventArgs e)
|
||||
{
|
||||
_hasExited = true;
|
||||
if (Exited != null)
|
||||
{
|
||||
Exited(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
public ProcessOptions StartInfo => _options;
|
||||
|
||||
public StreamWriter StandardInput => _process.StandardInput;
|
||||
|
||||
public StreamReader StandardError => _process.StandardError;
|
||||
|
||||
public StreamReader StandardOutput => _process.StandardOutput;
|
||||
|
||||
public int ExitCode => _process.ExitCode;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_process.Start();
|
||||
@@ -108,7 +100,7 @@ namespace Emby.Server.Implementations.Diagnostics
|
||||
|
||||
public Task<bool> WaitForExitAsync(int timeMs)
|
||||
{
|
||||
//Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
|
||||
// Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
|
||||
|
||||
if (HasExited)
|
||||
{
|
||||
@@ -130,7 +122,29 @@ namespace Emby.Server.Implementations.Diagnostics
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_process.Dispose();
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_process?.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private void OnProcessExited(object sender, EventArgs e)
|
||||
{
|
||||
_hasExited = true;
|
||||
Exited?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -21,8 +19,6 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -36,13 +32,9 @@ namespace Emby.Server.Implementations.Dto
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IProviderManager _providerManager;
|
||||
|
||||
private readonly Func<IChannelManager> _channelManagerFactory;
|
||||
private readonly IApplicationHost _appHost;
|
||||
private readonly Func<IDeviceManager> _deviceManager;
|
||||
private readonly Func<IMediaSourceManager> _mediaSourceManager;
|
||||
private readonly Func<ILiveTvManager> _livetvManager;
|
||||
|
||||
@@ -52,12 +44,8 @@ namespace Emby.Server.Implementations.Dto
|
||||
IUserDataManager userDataRepository,
|
||||
IItemRepository itemRepo,
|
||||
IImageProcessor imageProcessor,
|
||||
IServerConfigurationManager config,
|
||||
IFileSystem fileSystem,
|
||||
IProviderManager providerManager,
|
||||
Func<IChannelManager> channelManagerFactory,
|
||||
IApplicationHost appHost,
|
||||
Func<IDeviceManager> deviceManager,
|
||||
Func<IMediaSourceManager> mediaSourceManager,
|
||||
Func<ILiveTvManager> livetvManager)
|
||||
{
|
||||
@@ -66,12 +54,8 @@ namespace Emby.Server.Implementations.Dto
|
||||
_userDataRepository = userDataRepository;
|
||||
_itemRepo = itemRepo;
|
||||
_imageProcessor = imageProcessor;
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_providerManager = providerManager;
|
||||
_channelManagerFactory = channelManagerFactory;
|
||||
_appHost = appHost;
|
||||
_deviceManager = deviceManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_livetvManager = livetvManager;
|
||||
}
|
||||
@@ -95,15 +79,8 @@ namespace Emby.Server.Implementations.Dto
|
||||
return GetBaseItemDto(item, options, user, owner);
|
||||
}
|
||||
|
||||
public BaseItemDto[] GetBaseItemDtos(List<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
|
||||
{
|
||||
return GetBaseItemDtos(items, items.Count, options, user, owner);
|
||||
}
|
||||
|
||||
public BaseItemDto[] GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null)
|
||||
{
|
||||
return GetBaseItemDtos(items, items.Length, options, user, owner);
|
||||
}
|
||||
public BaseItemDto[] GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
|
||||
=> GetBaseItemDtos(items, items.Count, options, user, owner);
|
||||
|
||||
public BaseItemDto[] GetBaseItemDtos(IEnumerable<BaseItem> items, int itemCount, DtoOptions options, User user = null, BaseItem owner = null)
|
||||
{
|
||||
|
||||
@@ -9,12 +9,10 @@
|
||||
<ProjectReference Include="..\MediaBrowser.Providers\MediaBrowser.Providers.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj" />
|
||||
<ProjectReference Include="..\SocketHttpListener\SocketHttpListener.csproj" />
|
||||
<ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj" />
|
||||
<ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj" />
|
||||
<ProjectReference Include="..\OpenSubtitlesHandler\OpenSubtitlesHandler.csproj" />
|
||||
<ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj" />
|
||||
<ProjectReference Include="..\Emby.Drawing\Emby.Drawing.csproj" />
|
||||
<ProjectReference Include="..\Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj" />
|
||||
@@ -22,9 +20,19 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.22.0" />
|
||||
<PackageReference Include="SimpleInjector" Version="4.4.2" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="1.0.0" />
|
||||
<PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" />
|
||||
</ItemGroup>
|
||||
@@ -38,6 +46,21 @@
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Code analysers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.3" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Localization\iso6392.txt" />
|
||||
<EmbeddedResource Include="Localization\countries.json" />
|
||||
|
||||
@@ -388,7 +388,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
|
||||
FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(),
|
||||
|
||||
CollectionFolders = GetTopParentIds(newAndRemoved, user, allUserRootChildren).ToArray()
|
||||
CollectionFolders = GetTopParentIds(newAndRemoved, allUserRootChildren).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -407,7 +407,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
return item.SourceType == SourceType.Library;
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetTopParentIds(List<BaseItem> items, User user, List<Folder> allUserRootChildren)
|
||||
private IEnumerable<string> GetTopParentIds(List<BaseItem> items, List<Folder> allUserRootChildren)
|
||||
{
|
||||
var list = new List<string>();
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
class UserDataChangeNotifier : IServerEntryPoint
|
||||
public class UserDataChangeNotifier : IServerEntryPoint
|
||||
{
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using MediaBrowser.Model.System;
|
||||
|
||||
namespace Emby.Server.Implementations.EnvironmentInfo
|
||||
{
|
||||
public class EnvironmentInfo : IEnvironmentInfo
|
||||
{
|
||||
public EnvironmentInfo(MediaBrowser.Model.System.OperatingSystem operatingSystem)
|
||||
{
|
||||
OperatingSystem = operatingSystem;
|
||||
}
|
||||
|
||||
public MediaBrowser.Model.System.OperatingSystem OperatingSystem { get; private set; }
|
||||
|
||||
public string OperatingSystemName
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (OperatingSystem)
|
||||
{
|
||||
case MediaBrowser.Model.System.OperatingSystem.Android: return "Android";
|
||||
case MediaBrowser.Model.System.OperatingSystem.BSD: return "BSD";
|
||||
case MediaBrowser.Model.System.OperatingSystem.Linux: return "Linux";
|
||||
case MediaBrowser.Model.System.OperatingSystem.OSX: return "macOS";
|
||||
case MediaBrowser.Model.System.OperatingSystem.Windows: return "Windows";
|
||||
default: throw new Exception($"Unknown OS {OperatingSystem}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string OperatingSystemVersion => Environment.OSVersion.Version.ToString() + " " + Environment.OSVersion.ServicePack.ToString();
|
||||
|
||||
public Architecture SystemArchitecture => RuntimeInformation.OSArchitecture;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
namespace Emby.Server.Implementations.FFMpeg
|
||||
{
|
||||
/// <summary>
|
||||
/// Class FFMpegInfo
|
||||
/// </summary>
|
||||
public class FFMpegInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the path.
|
||||
/// </summary>
|
||||
/// <value>The path.</value>
|
||||
public string EncoderPath { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the probe path.
|
||||
/// </summary>
|
||||
/// <value>The probe path.</value>
|
||||
public string ProbePath { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the version.
|
||||
/// </summary>
|
||||
/// <value>The version.</value>
|
||||
public string Version { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace Emby.Server.Implementations.FFMpeg
|
||||
{
|
||||
public class FFMpegInstallInfo
|
||||
{
|
||||
public string Version { get; set; }
|
||||
public string FFMpegFilename { get; set; }
|
||||
public string FFProbeFilename { get; set; }
|
||||
public string ArchiveType { get; set; }
|
||||
|
||||
public FFMpegInstallInfo()
|
||||
{
|
||||
Version = "Path";
|
||||
FFMpegFilename = "ffmpeg";
|
||||
FFProbeFilename = "ffprobe";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.FFMpeg
|
||||
{
|
||||
public class FFMpegLoader
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IZipClient _zipClient;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly FFMpegInstallInfo _ffmpegInstallInfo;
|
||||
|
||||
public FFMpegLoader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem, FFMpegInstallInfo ffmpegInstallInfo)
|
||||
{
|
||||
_logger = logger;
|
||||
_appPaths = appPaths;
|
||||
_httpClient = httpClient;
|
||||
_zipClient = zipClient;
|
||||
_fileSystem = fileSystem;
|
||||
_ffmpegInstallInfo = ffmpegInstallInfo;
|
||||
}
|
||||
|
||||
public FFMpegInfo GetFFMpegInfo(IStartupOptions options)
|
||||
{
|
||||
var customffMpegPath = options.FFmpegPath;
|
||||
var customffProbePath = options.FFprobePath;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(customffMpegPath) && !string.IsNullOrWhiteSpace(customffProbePath))
|
||||
{
|
||||
return new FFMpegInfo
|
||||
{
|
||||
ProbePath = customffProbePath,
|
||||
EncoderPath = customffMpegPath,
|
||||
Version = "external"
|
||||
};
|
||||
}
|
||||
|
||||
var downloadInfo = _ffmpegInstallInfo;
|
||||
|
||||
var prebuiltFolder = _appPaths.ProgramSystemPath;
|
||||
var prebuiltffmpeg = Path.Combine(prebuiltFolder, downloadInfo.FFMpegFilename);
|
||||
var prebuiltffprobe = Path.Combine(prebuiltFolder, downloadInfo.FFProbeFilename);
|
||||
if (File.Exists(prebuiltffmpeg) && File.Exists(prebuiltffprobe))
|
||||
{
|
||||
return new FFMpegInfo
|
||||
{
|
||||
ProbePath = prebuiltffprobe,
|
||||
EncoderPath = prebuiltffmpeg,
|
||||
Version = "external"
|
||||
};
|
||||
}
|
||||
|
||||
var version = downloadInfo.Version;
|
||||
|
||||
if (string.Equals(version, "0", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new FFMpegInfo();
|
||||
}
|
||||
|
||||
var rootEncoderPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
|
||||
var versionedDirectoryPath = Path.Combine(rootEncoderPath, version);
|
||||
|
||||
var info = new FFMpegInfo
|
||||
{
|
||||
ProbePath = Path.Combine(versionedDirectoryPath, downloadInfo.FFProbeFilename),
|
||||
EncoderPath = Path.Combine(versionedDirectoryPath, downloadInfo.FFMpegFilename),
|
||||
Version = version
|
||||
};
|
||||
|
||||
Directory.CreateDirectory(versionedDirectoryPath);
|
||||
|
||||
var excludeFromDeletions = new List<string> { versionedDirectoryPath };
|
||||
|
||||
if (!File.Exists(info.ProbePath) || !File.Exists(info.EncoderPath))
|
||||
{
|
||||
// ffmpeg not present. See if there's an older version we can start with
|
||||
var existingVersion = GetExistingVersion(info, rootEncoderPath);
|
||||
|
||||
// No older version. Need to download and block until complete
|
||||
if (existingVersion == null)
|
||||
{
|
||||
return new FFMpegInfo();
|
||||
}
|
||||
else
|
||||
{
|
||||
info = existingVersion;
|
||||
versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath);
|
||||
excludeFromDeletions.Add(versionedDirectoryPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Allow just one of these to be overridden, if desired.
|
||||
if (!string.IsNullOrWhiteSpace(customffMpegPath))
|
||||
{
|
||||
info.EncoderPath = customffMpegPath;
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(customffProbePath))
|
||||
{
|
||||
info.ProbePath = customffProbePath;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private FFMpegInfo GetExistingVersion(FFMpegInfo info, string rootEncoderPath)
|
||||
{
|
||||
var encoderFilename = Path.GetFileName(info.EncoderPath);
|
||||
var probeFilename = Path.GetFileName(info.ProbePath);
|
||||
|
||||
foreach (var directory in _fileSystem.GetDirectoryPaths(rootEncoderPath)
|
||||
.ToList())
|
||||
{
|
||||
var allFiles = _fileSystem.GetFilePaths(directory, true).ToList();
|
||||
|
||||
var encoder = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), encoderFilename, StringComparison.OrdinalIgnoreCase));
|
||||
var probe = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), probeFilename, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(encoder) &&
|
||||
!string.IsNullOrWhiteSpace(probe))
|
||||
{
|
||||
return new FFMpegInfo
|
||||
{
|
||||
EncoderPath = encoder,
|
||||
ProbePath = probe,
|
||||
Version = Path.GetFileName(Path.GetDirectoryName(probe))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Emby.Server.Implementations.HttpClientManager
|
||||
{
|
||||
@@ -179,11 +180,11 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||
|
||||
foreach (var header in options.RequestHeaders)
|
||||
{
|
||||
if (string.Equals(header.Key, "Accept", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(header.Key, HeaderNames.Accept, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
request.Accept = header.Value;
|
||||
}
|
||||
else if (string.Equals(header.Key, "User-Agent", StringComparison.OrdinalIgnoreCase))
|
||||
else if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SetUserAgent(request, header.Value);
|
||||
hasUserAgent = true;
|
||||
@@ -327,7 +328,6 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||
}
|
||||
|
||||
httpWebRequest.ContentType = contentType;
|
||||
httpWebRequest.ContentLength = bytes.Length;
|
||||
(await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -539,21 +539,10 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||
|
||||
var contentLength = GetContentLength(httpResponse);
|
||||
|
||||
if (contentLength.HasValue)
|
||||
using (var stream = httpResponse.GetResponseStream())
|
||||
using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
|
||||
{
|
||||
using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
|
||||
{
|
||||
await httpResponse.GetResponseStream().CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're not able to track progress
|
||||
using (var stream = httpResponse.GetResponseStream())
|
||||
using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
|
||||
{
|
||||
await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
options.Progress.Report(100);
|
||||
|
||||
@@ -5,15 +5,19 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Emby.Server.Implementations.HttpServer
|
||||
{
|
||||
public class FileWriter : IHttpResult
|
||||
{
|
||||
private readonly IStreamHelper _streamHelper;
|
||||
private ILogger Logger { get; set; }
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
private string RangeHeader { get; set; }
|
||||
private bool IsHeadRequest { get; set; }
|
||||
@@ -42,25 +46,27 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
public string Path { get; set; }
|
||||
|
||||
public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem)
|
||||
public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem, IStreamHelper streamHelper)
|
||||
{
|
||||
if (string.IsNullOrEmpty(contentType))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contentType));
|
||||
}
|
||||
|
||||
_streamHelper = streamHelper;
|
||||
_fileSystem = fileSystem;
|
||||
|
||||
Path = path;
|
||||
Logger = logger;
|
||||
RangeHeader = rangeHeader;
|
||||
|
||||
Headers["Content-Type"] = contentType;
|
||||
Headers[HeaderNames.ContentType] = contentType;
|
||||
|
||||
TotalContentLength = fileSystem.GetFileInfo(path).Length;
|
||||
Headers["Accept-Ranges"] = "bytes";
|
||||
Headers[HeaderNames.AcceptRanges] = "bytes";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rangeHeader))
|
||||
{
|
||||
Headers["Content-Length"] = TotalContentLength.ToString(UsCulture);
|
||||
StatusCode = HttpStatusCode.OK;
|
||||
}
|
||||
else
|
||||
@@ -93,13 +99,10 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
RangeStart = requestedRange.Key;
|
||||
RangeLength = 1 + RangeEnd - RangeStart;
|
||||
|
||||
// Content-Length is the length of what we're serving, not the original content
|
||||
var lengthString = RangeLength.ToString(UsCulture);
|
||||
Headers["Content-Length"] = lengthString;
|
||||
var rangeString = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength);
|
||||
Headers["Content-Range"] = rangeString;
|
||||
var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}";
|
||||
Headers[HeaderNames.ContentRange] = rangeString;
|
||||
|
||||
Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString);
|
||||
Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Range: {2}", Path, RangeHeader, rangeString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -145,8 +148,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
}
|
||||
|
||||
private string[] SkipLogExtensions = new string[]
|
||||
{
|
||||
private static readonly string[] SkipLogExtensions = {
|
||||
".js",
|
||||
".html",
|
||||
".css"
|
||||
@@ -163,8 +165,10 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
|
||||
var path = Path;
|
||||
var offset = RangeStart;
|
||||
var count = RangeLength;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1))
|
||||
if (string.IsNullOrWhiteSpace(RangeHeader) || RangeStart <= 0 && RangeEnd >= TotalContentLength - 1)
|
||||
{
|
||||
var extension = System.IO.Path.GetExtension(path);
|
||||
|
||||
@@ -173,20 +177,15 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
Logger.LogDebug("Transmit file {0}", path);
|
||||
}
|
||||
|
||||
//var count = FileShare == FileShareMode.ReadWrite ? TotalContentLength : 0;
|
||||
|
||||
await response.TransmitFile(path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
offset = 0;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
await response.TransmitFile(path, RangeStart, RangeLength, FileShare, cancellationToken).ConfigureAwait(false);
|
||||
await response.TransmitFile(path, offset, count, FileShare, _fileSystem, _streamHelper, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (OnComplete != null)
|
||||
{
|
||||
OnComplete();
|
||||
}
|
||||
OnComplete?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,8 +202,5 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
get => (HttpStatusCode)Status;
|
||||
set => Status = (int)value;
|
||||
}
|
||||
|
||||
public string StatusDescription { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -10,6 +11,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Net;
|
||||
using Emby.Server.Implementations.Services;
|
||||
using Emby.Server.Implementations.SocketSharp;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
@@ -19,19 +21,20 @@ using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ServiceStack.Text.Jsv;
|
||||
|
||||
namespace Emby.Server.Implementations.HttpServer
|
||||
{
|
||||
public class HttpListenerHost : IHttpServer, IDisposable
|
||||
{
|
||||
private string DefaultRedirectPath { get; set; }
|
||||
|
||||
private readonly ILogger _logger;
|
||||
public string[] UrlPrefixes { get; private set; }
|
||||
|
||||
private IHttpListener _listener;
|
||||
|
||||
public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
|
||||
|
||||
private readonly IServerConfigurationManager _config;
|
||||
@@ -39,6 +42,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IXmlSerializer _xmlSerializer;
|
||||
private readonly IHttpListener _socketListener;
|
||||
private readonly Func<Type, Func<string, object>> _funcParseFn;
|
||||
|
||||
public Action<IRequest, IResponse, object>[] ResponseFilters { get; set; }
|
||||
@@ -53,20 +57,23 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
IServerApplicationHost applicationHost,
|
||||
ILoggerFactory loggerFactory,
|
||||
IServerConfigurationManager config,
|
||||
string defaultRedirectPath,
|
||||
IConfiguration configuration,
|
||||
INetworkManager networkManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IXmlSerializer xmlSerializer,
|
||||
Func<Type, Func<string, object>> funcParseFn)
|
||||
IHttpListener socketListener)
|
||||
{
|
||||
_appHost = applicationHost;
|
||||
_logger = loggerFactory.CreateLogger("HttpServer");
|
||||
Logger = loggerFactory.CreateLogger("HttpServer");
|
||||
_config = config;
|
||||
DefaultRedirectPath = defaultRedirectPath;
|
||||
DefaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"];
|
||||
_networkManager = networkManager;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_xmlSerializer = xmlSerializer;
|
||||
_funcParseFn = funcParseFn;
|
||||
_socketListener = socketListener;
|
||||
_socketListener.WebSocketConnected = OnWebSocketConnected;
|
||||
|
||||
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
|
||||
|
||||
Instance = this;
|
||||
ResponseFilters = Array.Empty<Action<IRequest, IResponse, object>>();
|
||||
@@ -74,7 +81,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
public string GlobalResponse { get; set; }
|
||||
|
||||
protected ILogger Logger => _logger;
|
||||
protected ILogger Logger { get; }
|
||||
|
||||
public object CreateInstance(Type type)
|
||||
{
|
||||
@@ -140,11 +147,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
return;
|
||||
}
|
||||
|
||||
var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger)
|
||||
var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, Logger)
|
||||
{
|
||||
OnReceive = ProcessWebSocketMessageReceived,
|
||||
Url = e.Url,
|
||||
QueryString = e.QueryString ?? new QueryParamCollection()
|
||||
QueryString = e.QueryString ?? new QueryCollection()
|
||||
};
|
||||
|
||||
connection.Closed += Connection_Closed;
|
||||
@@ -196,6 +203,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
case DirectoryNotFoundException _:
|
||||
case FileNotFoundException _:
|
||||
case ResourceNotFoundException _: return 404;
|
||||
case MethodNotAllowedException _: return 405;
|
||||
case RemoteServiceUnavailableException _: return 502;
|
||||
default: return 500;
|
||||
}
|
||||
@@ -209,16 +217,16 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
if (logExceptionStackTrace)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing request");
|
||||
Logger.LogError(ex, "Error processing request");
|
||||
}
|
||||
else if (logExceptionMessage)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
Logger.LogError(ex.Message);
|
||||
}
|
||||
|
||||
var httpRes = httpReq.Response;
|
||||
|
||||
if (httpRes.IsClosed)
|
||||
if (httpRes.OriginalResponse.HasStarted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -231,7 +239,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
catch (Exception errorEx)
|
||||
{
|
||||
_logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
|
||||
Logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,39 +282,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (_listener != null)
|
||||
{
|
||||
_logger.LogInformation("Stopping HttpListener...");
|
||||
var task = _listener.Stop();
|
||||
Task.WaitAll(task);
|
||||
_logger.LogInformation("HttpListener stopped");
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly string[] _skipLogExtensions =
|
||||
{
|
||||
".js",
|
||||
".css",
|
||||
".woff",
|
||||
".woff2",
|
||||
".ttf",
|
||||
".html"
|
||||
};
|
||||
|
||||
private bool EnableLogging(string url, string localPath)
|
||||
{
|
||||
var extension = GetExtension(url);
|
||||
|
||||
return ((string.IsNullOrEmpty(extension) || !_skipLogExtensions.Contains(extension))
|
||||
&& (string.IsNullOrEmpty(localPath) || localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1));
|
||||
}
|
||||
|
||||
private static string GetExtension(string url)
|
||||
{
|
||||
var parts = url.Split(new[] { '?' }, 2);
|
||||
|
||||
return Path.GetExtension(parts[0]);
|
||||
}
|
||||
|
||||
public static string RemoveQueryStringByKey(string url, string key)
|
||||
@@ -314,7 +289,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
var uri = new Uri(url);
|
||||
|
||||
// this gets all the query string key value pairs as a collection
|
||||
var newQueryString = MyHttpUtility.ParseQueryString(uri.Query);
|
||||
var newQueryString = QueryHelpers.ParseQuery(uri.Query);
|
||||
|
||||
var originalCount = newQueryString.Count;
|
||||
|
||||
@@ -335,7 +310,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
string pagePathWithoutQueryString = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0];
|
||||
|
||||
return newQueryString.Count > 0
|
||||
? string.Format("{0}?{1}", pagePathWithoutQueryString, newQueryString)
|
||||
? QueryHelpers.AddQueryString(pagePathWithoutQueryString, newQueryString.ToDictionary(kv => kv.Key, kv => kv.Value.ToString()))
|
||||
: pagePathWithoutQueryString;
|
||||
}
|
||||
|
||||
@@ -444,12 +419,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// <summary>
|
||||
/// Overridable method that can be used to implement a custom hnandler
|
||||
/// </summary>
|
||||
protected async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
|
||||
public async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
|
||||
{
|
||||
var date = DateTime.Now;
|
||||
var stopWatch = new Stopwatch();
|
||||
stopWatch.Start();
|
||||
var httpRes = httpReq.Response;
|
||||
bool enableLog = false;
|
||||
bool logHeaders = false;
|
||||
string urlToLog = null;
|
||||
string remoteIp = httpReq.RemoteIp;
|
||||
|
||||
@@ -496,18 +470,8 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
return;
|
||||
}
|
||||
|
||||
var operationName = httpReq.OperationName;
|
||||
|
||||
enableLog = EnableLogging(urlString, localPath);
|
||||
urlToLog = urlString;
|
||||
logHeaders = enableLog && urlToLog.IndexOf("/videos/", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
|
||||
if (enableLog)
|
||||
{
|
||||
urlToLog = GetUrlToLog(urlString);
|
||||
|
||||
LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent, logHeaders ? httpReq.Headers : null);
|
||||
}
|
||||
urlToLog = GetUrlToLog(urlString);
|
||||
Logger.LogDebug("HTTP {HttpMethod} {Url} UserAgent: {UserAgent} \nHeaders: {@Headers}", urlToLog, httpReq.UserAgent ?? string.Empty, httpReq.HttpMethod, httpReq.Headers);
|
||||
|
||||
if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -515,6 +479,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
RedirectToUrl(httpRes, DefaultRedirectPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -560,16 +525,19 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
RedirectToUrl(httpRes, DefaultRedirectPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
RedirectToUrl(httpRes, "../" + DefaultRedirectPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
RedirectToUrl(httpRes, DefaultRedirectPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(localPath))
|
||||
{
|
||||
RedirectToUrl(httpRes, "/" + DefaultRedirectPath);
|
||||
@@ -605,33 +573,21 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
if (handler != null)
|
||||
{
|
||||
await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, operationName, cancellationToken).ConfigureAwait(false);
|
||||
await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, httpReq.OperationName, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ErrorHandler(new FileNotFoundException(), httpReq, false, false).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
catch (Exception ex) when (ex is SocketException || ex is IOException || ex is OperationCanceledException)
|
||||
{
|
||||
await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
catch (IOException ex)
|
||||
{
|
||||
await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
catch (SocketException ex)
|
||||
{
|
||||
await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
catch (SecurityException ex)
|
||||
{
|
||||
await ErrorHandler(ex, httpReq, false, true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase);
|
||||
@@ -640,15 +596,15 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
finally
|
||||
{
|
||||
httpRes.Close();
|
||||
|
||||
if (enableLog)
|
||||
stopWatch.Stop();
|
||||
var elapsed = stopWatch.Elapsed;
|
||||
if (elapsed.TotalMilliseconds > 500)
|
||||
{
|
||||
var statusCode = httpRes.StatusCode;
|
||||
|
||||
var duration = DateTime.Now - date;
|
||||
|
||||
LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration, logHeaders ? httpRes.Headers : null);
|
||||
Logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -661,12 +617,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
var pathParts = pathInfo.TrimStart('/').Split('/');
|
||||
if (pathParts.Length == 0)
|
||||
{
|
||||
_logger.LogError("Path parts empty for PathInfo: {pathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl);
|
||||
Logger.LogError("Path parts empty for PathInfo: {PathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl);
|
||||
return null;
|
||||
}
|
||||
|
||||
var restPath = ServiceHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, out string contentType);
|
||||
|
||||
if (restPath != null)
|
||||
{
|
||||
return new ServiceHandler
|
||||
@@ -676,15 +631,13 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
};
|
||||
}
|
||||
|
||||
_logger.LogError("Could not find handler for {PathInfo}", pathInfo);
|
||||
Logger.LogError("Could not find handler for {PathInfo}", pathInfo);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Task Write(IResponse response, string text)
|
||||
{
|
||||
var bOutput = Encoding.UTF8.GetBytes(text);
|
||||
response.SetContentLength(bOutput.Length);
|
||||
|
||||
return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length);
|
||||
}
|
||||
|
||||
@@ -703,6 +656,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO what is this?
|
||||
var httpsUrl = url
|
||||
.Replace("http://", "https://", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace(":" + _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture), ":" + _config.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase);
|
||||
@@ -723,13 +677,15 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// Adds the rest handlers.
|
||||
/// </summary>
|
||||
/// <param name="services">The services.</param>
|
||||
public void Init(IEnumerable<IService> services, IEnumerable<IWebSocketListener> listeners)
|
||||
/// <param name="listeners"></param>
|
||||
/// <param name="urlPrefixes"></param>
|
||||
public void Init(IEnumerable<IService> services, IEnumerable<IWebSocketListener> listeners, IEnumerable<string> urlPrefixes)
|
||||
{
|
||||
_webSocketListeners = listeners.ToArray();
|
||||
|
||||
UrlPrefixes = urlPrefixes.ToArray();
|
||||
ServiceController = new ServiceController();
|
||||
|
||||
_logger.LogInformation("Calling ServiceStack AppHost.Init");
|
||||
Logger.LogInformation("Calling ServiceStack AppHost.Init");
|
||||
|
||||
var types = services.Select(r => r.GetType()).ToArray();
|
||||
|
||||
@@ -737,7 +693,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
ResponseFilters = new Action<IRequest, IResponse, object>[]
|
||||
{
|
||||
new ResponseFilter(_logger).FilterResponse
|
||||
new ResponseFilter(Logger).FilterResponse
|
||||
};
|
||||
}
|
||||
|
||||
@@ -799,8 +755,12 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
return _jsonSerializer.DeserializeFromStreamAsync(stream, type);
|
||||
}
|
||||
|
||||
//TODO Add Jellyfin Route Path Normalizer
|
||||
public Task ProcessWebSocketRequest(HttpContext context)
|
||||
{
|
||||
return _socketListener.ProcessWebSocketRequest(context);
|
||||
}
|
||||
|
||||
//TODO Add Jellyfin Route Path Normalizer
|
||||
private static string NormalizeEmbyRoutePath(string path)
|
||||
{
|
||||
if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -833,6 +793,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
private bool _disposed;
|
||||
private readonly object _disposeLock = new object();
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
@@ -861,7 +822,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Websocket message received: {0}", result.MessageType);
|
||||
Logger.LogDebug("Websocket message received: {0}", result.MessageType);
|
||||
|
||||
var tasks = _webSocketListeners.Select(i => Task.Run(async () =>
|
||||
{
|
||||
@@ -871,7 +832,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{0} failed processing WebSocket message {1}", i.GetType().Name, result.MessageType ?? string.Empty);
|
||||
Logger.LogError(ex, "{0} failed processing WebSocket message {1}", i.GetType().Name, result.MessageType ?? string.Empty);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -882,18 +843,5 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
public void StartServer(string[] urlPrefixes, IHttpListener httpListener)
|
||||
{
|
||||
UrlPrefixes = urlPrefixes;
|
||||
|
||||
_listener = httpListener;
|
||||
|
||||
_listener.WebSocketConnected = OnWebSocketConnected;
|
||||
_listener.ErrorHandler = ErrorHandler;
|
||||
_listener.RequestHandler = RequestHandler;
|
||||
|
||||
_listener.Start(UrlPrefixes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using IRequest = MediaBrowser.Model.Services.IRequest;
|
||||
using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
|
||||
|
||||
@@ -32,17 +34,16 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
private IBrotliCompressor _brotliCompressor;
|
||||
private readonly IStreamHelper _streamHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpResultFactory" /> class.
|
||||
/// </summary>
|
||||
public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IBrotliCompressor brotliCompressor)
|
||||
public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IStreamHelper streamHelper)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_brotliCompressor = brotliCompressor;
|
||||
_streamHelper = streamHelper;
|
||||
_logger = loggerfactory.CreateLogger("HttpResultFactory");
|
||||
}
|
||||
|
||||
@@ -76,7 +77,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
public object GetRedirectResult(string url)
|
||||
{
|
||||
var responseHeaders = new Dictionary<string, string>();
|
||||
responseHeaders["Location"] = url;
|
||||
responseHeaders[HeaderNames.Location] = url;
|
||||
|
||||
var result = new HttpResult(Array.Empty<byte>(), "text/plain", HttpStatusCode.Redirect);
|
||||
|
||||
@@ -90,16 +91,16 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// </summary>
|
||||
private IHasHeaders GetHttpResult(IRequest requestContext, Stream content, string contentType, bool addCachePrevention, IDictionary<string, string> responseHeaders = null)
|
||||
{
|
||||
var result = new StreamWriter(content, contentType, _logger);
|
||||
var result = new StreamWriter(content, contentType);
|
||||
|
||||
if (responseHeaders == null)
|
||||
{
|
||||
responseHeaders = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires))
|
||||
if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string expires))
|
||||
{
|
||||
responseHeaders["Expires"] = "-1";
|
||||
responseHeaders[HeaderNames.Expires] = "0";
|
||||
}
|
||||
|
||||
AddResponseHeaders(result, responseHeaders);
|
||||
@@ -131,7 +132,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
content = Array.Empty<byte>();
|
||||
}
|
||||
|
||||
result = new StreamWriter(content, contentType, contentLength, _logger);
|
||||
result = new StreamWriter(content, contentType);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -143,9 +144,9 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
responseHeaders = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires))
|
||||
if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string _))
|
||||
{
|
||||
responseHeaders["Expires"] = "-1";
|
||||
responseHeaders[HeaderNames.Expires] = "0";
|
||||
}
|
||||
|
||||
AddResponseHeaders(result, responseHeaders);
|
||||
@@ -175,7 +176,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
bytes = Array.Empty<byte>();
|
||||
}
|
||||
|
||||
result = new StreamWriter(bytes, contentType, contentLength, _logger);
|
||||
result = new StreamWriter(bytes, contentType);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -187,9 +188,9 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
responseHeaders = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires))
|
||||
if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string _))
|
||||
{
|
||||
responseHeaders["Expires"] = "-1";
|
||||
responseHeaders[HeaderNames.Expires] = "0";
|
||||
}
|
||||
|
||||
AddResponseHeaders(result, responseHeaders);
|
||||
@@ -214,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
responseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
responseHeaders["Expires"] = "-1";
|
||||
responseHeaders[HeaderNames.Expires] = "0";
|
||||
|
||||
return ToOptimizedResultInternal(requestContext, result, responseHeaders);
|
||||
}
|
||||
@@ -246,9 +247,9 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
private static string GetCompressionType(IRequest request)
|
||||
{
|
||||
var acceptEncoding = request.Headers["Accept-Encoding"];
|
||||
var acceptEncoding = request.Headers[HeaderNames.AcceptEncoding].ToString();
|
||||
|
||||
if (acceptEncoding != null)
|
||||
if (string.IsNullOrEmpty(acceptEncoding))
|
||||
{
|
||||
//if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
// return "br";
|
||||
@@ -277,9 +278,10 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
private object ToOptimizedResultInternal<T>(IRequest request, T dto, IDictionary<string, string> responseHeaders = null)
|
||||
{
|
||||
var contentType = request.ResponseContentType;
|
||||
// TODO: @bond use Span and .Equals
|
||||
var contentType = request.ResponseContentType?.Split(';')[0].Trim().ToLowerInvariant();
|
||||
|
||||
switch (GetRealContentType(contentType))
|
||||
switch (contentType)
|
||||
{
|
||||
case "application/xml":
|
||||
case "text/xml":
|
||||
@@ -325,21 +327,21 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
|
||||
content = Compress(content, requestedCompressionType);
|
||||
responseHeaders["Content-Encoding"] = requestedCompressionType;
|
||||
responseHeaders[HeaderNames.ContentEncoding] = requestedCompressionType;
|
||||
|
||||
responseHeaders["Vary"] = "Accept-Encoding";
|
||||
responseHeaders[HeaderNames.Vary] = HeaderNames.AcceptEncoding;
|
||||
|
||||
var contentLength = content.Length;
|
||||
|
||||
if (isHeadRequest)
|
||||
{
|
||||
var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength, _logger);
|
||||
var result = new StreamWriter(Array.Empty<byte>(), contentType);
|
||||
AddResponseHeaders(result, responseHeaders);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = new StreamWriter(content, contentType, contentLength, _logger);
|
||||
var result = new StreamWriter(content, contentType);
|
||||
AddResponseHeaders(result, responseHeaders);
|
||||
return result;
|
||||
}
|
||||
@@ -347,23 +349,19 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
private byte[] Compress(byte[] bytes, string compressionType)
|
||||
{
|
||||
if (string.Equals(compressionType, "br", StringComparison.OrdinalIgnoreCase))
|
||||
return CompressBrotli(bytes);
|
||||
|
||||
if (string.Equals(compressionType, "deflate", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Deflate(bytes);
|
||||
}
|
||||
|
||||
if (string.Equals(compressionType, "gzip", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GZip(bytes);
|
||||
}
|
||||
|
||||
throw new NotSupportedException(compressionType);
|
||||
}
|
||||
|
||||
private byte[] CompressBrotli(byte[] bytes)
|
||||
{
|
||||
return _brotliCompressor.Compress(bytes);
|
||||
}
|
||||
|
||||
private static byte[] Deflate(byte[] bytes)
|
||||
{
|
||||
// In .NET FX incompat-ville, you can't access compressed bytes without closing DeflateStream
|
||||
@@ -390,13 +388,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetRealContentType(string contentType)
|
||||
{
|
||||
return contentType == null
|
||||
? null
|
||||
: contentType.Split(';')[0].ToLowerInvariant().Trim();
|
||||
}
|
||||
|
||||
private static string SerializeToXmlString(object from)
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
@@ -424,12 +415,12 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// </summary>
|
||||
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, StaticResultOptions options)
|
||||
{
|
||||
bool noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
bool noCache = (requestContext.Headers[HeaderNames.CacheControl].ToString()).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
AddCachingHeaders(responseHeaders, options.CacheDuration, noCache, options.DateLastModified);
|
||||
|
||||
if (!noCache)
|
||||
{
|
||||
DateTime.TryParse(requestContext.Headers.Get("If-Modified-Since"), out var ifModifiedSinceHeader);
|
||||
DateTime.TryParse(requestContext.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader);
|
||||
|
||||
if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified))
|
||||
{
|
||||
@@ -530,7 +521,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var contentType = options.ContentType;
|
||||
if (!string.IsNullOrEmpty(requestContext.Headers.Get("If-Modified-Since")))
|
||||
if (!StringValues.IsNullOrEmpty(requestContext.Headers[HeaderNames.IfModifiedSince]))
|
||||
{
|
||||
// See if the result is already cached in the browser
|
||||
var result = GetCachedResult(requestContext, options.ResponseHeaders, options);
|
||||
@@ -548,11 +539,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
AddCachingHeaders(responseHeaders, options.CacheDuration, false, options.DateLastModified);
|
||||
AddAgeHeader(responseHeaders, options.DateLastModified);
|
||||
|
||||
var rangeHeader = requestContext.Headers.Get("Range");
|
||||
var rangeHeader = requestContext.Headers[HeaderNames.Range];
|
||||
|
||||
if (!isHeadRequest && !string.IsNullOrEmpty(options.Path))
|
||||
{
|
||||
var hasHeaders = new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem)
|
||||
var hasHeaders = new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem, _streamHelper)
|
||||
{
|
||||
OnComplete = options.OnComplete,
|
||||
OnError = options.OnError,
|
||||
@@ -590,11 +581,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
else
|
||||
{
|
||||
if (totalContentLength.HasValue)
|
||||
{
|
||||
responseHeaders["Content-Length"] = totalContentLength.Value.ToString(UsCulture);
|
||||
}
|
||||
|
||||
if (isHeadRequest)
|
||||
{
|
||||
using (stream)
|
||||
@@ -603,7 +589,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
}
|
||||
|
||||
var hasHeaders = new StreamWriter(stream, contentType, _logger)
|
||||
var hasHeaders = new StreamWriter(stream, contentType)
|
||||
{
|
||||
OnComplete = options.OnComplete,
|
||||
OnError = options.OnError
|
||||
@@ -614,11 +600,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The us culture
|
||||
/// </summary>
|
||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
/// <summary>
|
||||
/// Adds the caching responseHeaders.
|
||||
/// </summary>
|
||||
@@ -627,23 +608,23 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
{
|
||||
if (noCache)
|
||||
{
|
||||
responseHeaders["Cache-Control"] = "no-cache, no-store, must-revalidate";
|
||||
responseHeaders["pragma"] = "no-cache, no-store, must-revalidate";
|
||||
responseHeaders[HeaderNames.CacheControl] = "no-cache, no-store, must-revalidate";
|
||||
responseHeaders[HeaderNames.Pragma] = "no-cache, no-store, must-revalidate";
|
||||
return;
|
||||
}
|
||||
|
||||
if (cacheDuration.HasValue)
|
||||
{
|
||||
responseHeaders["Cache-Control"] = "public, max-age=" + cacheDuration.Value.TotalSeconds;
|
||||
responseHeaders[HeaderNames.CacheControl] = "public, max-age=" + cacheDuration.Value.TotalSeconds;
|
||||
}
|
||||
else
|
||||
{
|
||||
responseHeaders["Cache-Control"] = "public";
|
||||
responseHeaders[HeaderNames.CacheControl] = "public";
|
||||
}
|
||||
|
||||
if (lastModifiedDate.HasValue)
|
||||
{
|
||||
responseHeaders["Last-Modified"] = lastModifiedDate.ToString();
|
||||
responseHeaders[HeaderNames.LastModified] = lastModifiedDate.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -656,7 +637,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
{
|
||||
if (lastDateModified.HasValue)
|
||||
{
|
||||
responseHeaders["Age"] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture);
|
||||
responseHeaders[HeaderNames.Age] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,9 +695,4 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IBrotliCompressor
|
||||
{
|
||||
byte[] Compress(byte[] content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Net;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Emby.Server.Implementations.HttpServer
|
||||
{
|
||||
@@ -28,21 +27,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// <value>The web socket handler.</value>
|
||||
Action<WebSocketConnectEventArgs> WebSocketConnected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the web socket connecting.
|
||||
/// </summary>
|
||||
/// <value>The web socket connecting.</value>
|
||||
Action<WebSocketConnectingEventArgs> WebSocketConnecting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts this instance.
|
||||
/// </summary>
|
||||
/// <param name="urlPrefixes">The URL prefixes.</param>
|
||||
void Start(IEnumerable<string> urlPrefixes);
|
||||
|
||||
/// <summary>
|
||||
/// Stops this instance.
|
||||
/// </summary>
|
||||
Task Stop();
|
||||
|
||||
Task ProcessWebSocketRequest(HttpContext ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.HttpServer
|
||||
{
|
||||
public static class LoggerUtils
|
||||
{
|
||||
public static void LogRequest(ILogger logger, string url, string method, string userAgent, QueryParamCollection headers)
|
||||
{
|
||||
if (headers == null)
|
||||
{
|
||||
logger.LogInformation("{0} {1}. UserAgent: {2}", "HTTP " + method, url, userAgent ?? string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
var headerText = string.Empty;
|
||||
var index = 0;
|
||||
|
||||
foreach (var i in headers)
|
||||
{
|
||||
if (index > 0)
|
||||
{
|
||||
headerText += ", ";
|
||||
}
|
||||
|
||||
headerText += i.Name + "=" + i.Value;
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
logger.LogInformation("HTTP {0} {1}. {2}", method, url, headerText);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs the response.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="statusCode">The status code.</param>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="endPoint">The end point.</param>
|
||||
/// <param name="duration">The duration.</param>
|
||||
public static void LogResponse(ILogger logger, int statusCode, string url, string endPoint, TimeSpan duration, QueryParamCollection headers)
|
||||
{
|
||||
var durationMs = duration.TotalMilliseconds;
|
||||
var logSuffix = durationMs >= 1000 && durationMs < 60000 ? "ms (slow)" : "ms";
|
||||
|
||||
//var headerText = headers == null ? string.Empty : "Headers: " + string.Join(", ", headers.Where(i => i.Name.IndexOf("Access-", StringComparison.OrdinalIgnoreCase) == -1).Select(i => i.Name + "=" + i.Value).ToArray());
|
||||
var headerText = string.Empty;
|
||||
logger.LogInformation("HTTP Response {0} to {1}. Time: {2}{3}. {4} {5}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url, headerText);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user