mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-12-12 20:13:01 +03:00
Compare commits
244 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a15098dc00 | ||
|
|
86a4d15a32 | ||
|
|
b86f049e66 | ||
|
|
12fea24590 | ||
|
|
26e2ffdd31 | ||
|
|
d9ab654abe | ||
|
|
c44f96b727 | ||
|
|
c5ac36c886 | ||
|
|
ff9a0c7e55 | ||
|
|
056e19f350 | ||
|
|
f8ba55e202 | ||
|
|
4ea76f9cdc | ||
|
|
88f56cd0c4 | ||
|
|
6d74184cfb | ||
|
|
2845e7e101 | ||
|
|
c78298789d | ||
|
|
7bb8985f11 | ||
|
|
8414285b58 | ||
|
|
7ec42b89a0 | ||
|
|
c720504e39 | ||
|
|
af8f86b3de | ||
|
|
bfc3954995 | ||
|
|
4ed90d4658 | ||
|
|
3a6a5baa8e | ||
|
|
18f0d996c0 | ||
|
|
da2554bd53 | ||
|
|
f2811323c2 | ||
|
|
221a95c93c | ||
|
|
982ac32471 | ||
|
|
d48275a785 | ||
|
|
a4e98a0390 | ||
|
|
0fbdb79df7 | ||
|
|
87dc60d4aa | ||
|
|
d6b56dde62 | ||
|
|
0302144b22 | ||
|
|
269b94254b | ||
|
|
2f377e0a0f | ||
|
|
3b96c78515 | ||
|
|
e150174ece | ||
|
|
33e069e461 | ||
|
|
593b7327cf | ||
|
|
9d795adc3e | ||
|
|
0ff38b6012 | ||
|
|
fe43e279c8 | ||
|
|
d18823ced1 | ||
|
|
db4a72df10 | ||
|
|
406fb045c2 | ||
|
|
32992b6143 | ||
|
|
da169dddb5 | ||
|
|
250f03d2d9 | ||
|
|
d8cb34dbbc | ||
|
|
8fd9f5b6a4 | ||
|
|
2f4a00d322 | ||
|
|
9849c183ac | ||
|
|
bcb32ec6ad | ||
|
|
eb4b705167 | ||
|
|
d6c669a7c8 | ||
|
|
1b84446831 | ||
|
|
fb256b7aa0 | ||
|
|
382b8bb509 | ||
|
|
00234a5ece | ||
|
|
57cefb432a | ||
|
|
7be4a8500c | ||
|
|
74d2698c5f | ||
|
|
4727f69fc9 | ||
|
|
1e6c41e333 | ||
|
|
bee8f58265 | ||
|
|
a71040ba1b | ||
|
|
f1ef0b0b4c | ||
|
|
387b4dea25 | ||
|
|
3014866f65 | ||
|
|
37985c2e26 | ||
|
|
139e3c19ee | ||
|
|
da860e6e54 | ||
|
|
ce03662fa7 | ||
|
|
49923e50db | ||
|
|
c3c52b6682 | ||
|
|
80281e599d | ||
|
|
3a88a3c795 | ||
|
|
d6e98f9a50 | ||
|
|
2bcbffee0c | ||
|
|
22ffc5aee4 | ||
|
|
ee3a4531a0 | ||
|
|
aff740c596 | ||
|
|
38d9eeffbe | ||
|
|
d6d9fce898 | ||
|
|
f60ad53393 | ||
|
|
e8461d3317 | ||
|
|
9dd512df80 | ||
|
|
5b0bd88892 | ||
|
|
70c85925af | ||
|
|
e449182641 | ||
|
|
fcfe02ee73 | ||
|
|
b8b650540d | ||
|
|
e1d523ee45 | ||
|
|
ceb8b9f740 | ||
|
|
8413c56392 | ||
|
|
88038d9644 | ||
|
|
c846da4f9e | ||
|
|
546f4cd46f | ||
|
|
e216702bcf | ||
|
|
be89a5e719 | ||
|
|
84d56976ba | ||
|
|
0ef2b46106 | ||
|
|
52294881b1 | ||
|
|
5e8a2db029 | ||
|
|
262eefd8db | ||
|
|
73e8758d84 | ||
|
|
181de97ce5 | ||
|
|
ae5514afd6 | ||
|
|
83af2db679 | ||
|
|
0b3e6548db | ||
|
|
72beadc74d | ||
|
|
f0e74c2c6b | ||
|
|
d351fa0c1e | ||
|
|
20033f2275 | ||
|
|
c4c0894b29 | ||
|
|
c4f51e16a5 | ||
|
|
56dcc45dc0 | ||
|
|
be89d53a9e | ||
|
|
3ac7531385 | ||
|
|
cb1ff69585 | ||
|
|
a50fb922c5 | ||
|
|
593c6c071c | ||
|
|
d573f2d671 | ||
|
|
1ce5939362 | ||
|
|
4d335d8f13 | ||
|
|
c118f111b6 | ||
|
|
52e91243e5 | ||
|
|
9faa68b26f | ||
|
|
aadf7676d1 | ||
|
|
08ca1337a9 | ||
|
|
07072d9f7b | ||
|
|
7e3c45c917 | ||
|
|
548270772c | ||
|
|
cb7bffc233 | ||
|
|
da2caa2902 | ||
|
|
ab0e851db9 | ||
|
|
32f393d57f | ||
|
|
78e4e2ed92 | ||
|
|
42d5a48491 | ||
|
|
20dac6d6b8 | ||
|
|
1cdcace061 | ||
|
|
95ee3c72e3 | ||
|
|
66eabcdd39 | ||
|
|
1d94607a30 | ||
|
|
1385d89df6 | ||
|
|
8b073e2ba5 | ||
|
|
34da7de47d | ||
|
|
021a1887fb | ||
|
|
6772ac5603 | ||
|
|
b630e9de82 | ||
|
|
7774977cdd | ||
|
|
5ac6d0ae59 | ||
|
|
fa3a8108e5 | ||
|
|
660f6174b3 | ||
|
|
8af1e93cd4 | ||
|
|
cabb824f2a | ||
|
|
b4c5ff89fd | ||
|
|
78324ff797 | ||
|
|
885a000da7 | ||
|
|
aad34e62ca | ||
|
|
45d8ace9bb | ||
|
|
5e4697802f | ||
|
|
f6227e99cc | ||
|
|
ae24d644db | ||
|
|
b982d7c239 | ||
|
|
c713824bf9 | ||
|
|
ea851317e7 | ||
|
|
8985fb8d58 | ||
|
|
b5e8cce4cf | ||
|
|
211ae30188 | ||
|
|
e18b89ca27 | ||
|
|
ebd2a30087 | ||
|
|
fd361421b1 | ||
|
|
f7a46c7a56 | ||
|
|
2a1f6361a5 | ||
|
|
1ea219bf3f | ||
|
|
d0f2b3a747 | ||
|
|
ffcf6bdd3a | ||
|
|
795f2c8774 | ||
|
|
f8aff0c51d | ||
|
|
055e43eda7 | ||
|
|
c8cb908004 | ||
|
|
8ab08dd041 | ||
|
|
8487319374 | ||
|
|
91e99effc9 | ||
|
|
0e2e731103 | ||
|
|
6822975fd3 | ||
|
|
12e4c1c7ae | ||
|
|
6786dfcabd | ||
|
|
f06b9a14f3 | ||
|
|
838541b825 | ||
|
|
1d1d7e8a37 | ||
|
|
d3afa53191 | ||
|
|
450f246f95 | ||
|
|
b9a111432a | ||
|
|
581a7fe078 | ||
|
|
a430568082 | ||
|
|
d7c6d16250 | ||
|
|
3a831994f6 | ||
|
|
dc68d61491 | ||
|
|
a05d803d4c | ||
|
|
b4893b9ac9 | ||
|
|
b0608d26b4 | ||
|
|
67b1f9f716 | ||
|
|
39195aae09 | ||
|
|
cc598a86f1 | ||
|
|
ffe79c8982 | ||
|
|
05ad2e9b3f | ||
|
|
42abb5a993 | ||
|
|
a4055779f6 | ||
|
|
1a3543e5a5 | ||
|
|
42e0b32c7d | ||
|
|
85a58fd655 | ||
|
|
a709cbdc64 | ||
|
|
08b63a7c11 | ||
|
|
51edd5d067 | ||
|
|
ee89236fe8 | ||
|
|
fee42e883c | ||
|
|
cc3422b96b | ||
|
|
50279be686 | ||
|
|
0e617933f6 | ||
|
|
f74bfcb343 | ||
|
|
64b6cfa3dc | ||
|
|
bb056f4b59 | ||
|
|
ce11869a1a | ||
|
|
e3b19c22a7 | ||
|
|
05fd76c0fa | ||
|
|
7165868509 | ||
|
|
4190410c7e | ||
|
|
b673054c8d | ||
|
|
e051ca6ff6 | ||
|
|
d8d6c6f254 | ||
|
|
2ffab720fb | ||
|
|
07f163a4c3 | ||
|
|
883575893b | ||
|
|
d1a0497f55 | ||
|
|
ded9dee22c | ||
|
|
e8f6a61131 | ||
|
|
fd7f420af2 | ||
|
|
eaa6cb0ddc | ||
|
|
8af256f9c2 | ||
|
|
9f83ee7b3e |
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -8,23 +8,23 @@ assignees: ''
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
<!-- Steps to reproduce the behavior: -->
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
**Logs**
|
||||
Please paste any log errors.
|
||||
<!-- Please paste any log errors. -->
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**System (please complete the following information):**
|
||||
- OS: [e.g. Docker, Debian, Windows]
|
||||
@@ -32,4 +32,4 @@ If applicable, add screenshots to help explain your problem.
|
||||
- Jellyfin Version: [e.g. 10.0.1]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
<!-- Add any other context about the problem here. -->
|
||||
|
||||
@@ -8,13 +8,13 @@ assignees: ''
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -8,7 +8,7 @@ assignees: ''
|
||||
---
|
||||
|
||||
**Describe the feature you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
||||
8
.github/pull_request_template.md
vendored
8
.github/pull_request_template.md
vendored
@@ -1,9 +1,11 @@
|
||||
<!--
|
||||
Ensure your title is short, descriptive, and in the imperative mood (Fix X, Change Y, instead of Fixed X, Changed Y).
|
||||
For a good inspiration of what to write in commit messages and PRs please review https://chris.beams.io/posts/git-commit/ and our https://jellyfin.readthedocs.io/en/latest/developer-docs/contributing/ page.
|
||||
-->
|
||||
|
||||
**Changes**
|
||||
Describe your changes here in 1-5 sentences.
|
||||
<!-- Describe your changes here in 1-5 sentences. -->
|
||||
|
||||
**Issues**
|
||||
Tag any issues that this PR solves here.
|
||||
Fixes #
|
||||
<!-- Tag any issues that this PR solves here.
|
||||
ex. Fixes # -->
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace BDInfo
|
||||
}
|
||||
|
||||
DirectoryRoot =
|
||||
_fileSystem.GetDirectoryInfo(_fileSystem.GetDirectoryName(DirectoryBDMV.FullName));
|
||||
_fileSystem.GetDirectoryInfo(Path.GetDirectoryName(DirectoryBDMV.FullName));
|
||||
DirectoryBDJO =
|
||||
GetDirectory("BDJO", DirectoryBDMV, 0);
|
||||
DirectoryCLIPINF =
|
||||
@@ -150,7 +150,7 @@ namespace BDInfo
|
||||
Is3D = true;
|
||||
}
|
||||
|
||||
if (_fileSystem.FileExists(Path.Combine(DirectoryRoot.FullName, "FilmIndex.xml")))
|
||||
if (File.Exists(Path.Combine(DirectoryRoot.FullName, "FilmIndex.xml")))
|
||||
{
|
||||
IsDBOX = true;
|
||||
}
|
||||
@@ -345,7 +345,7 @@ namespace BDInfo
|
||||
{
|
||||
return dir;
|
||||
}
|
||||
var parentFolder = _fileSystem.GetDirectoryName(dir.FullName);
|
||||
var parentFolder = Path.GetDirectoryName(dir.FullName);
|
||||
if (string.IsNullOrEmpty(parentFolder))
|
||||
{
|
||||
dir = null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//============================================================================
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
@@ -231,7 +231,7 @@ namespace BDInfo
|
||||
Streams.Clear();
|
||||
StreamClips.Clear();
|
||||
|
||||
fileStream = _fileSystem.OpenRead(FileInfo.FullName);
|
||||
fileStream = File.OpenRead(FileInfo.FullName);
|
||||
fileReader = new BinaryReader(fileStream);
|
||||
|
||||
byte[] data = new byte[fileStream.Length];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//============================================================================
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
@@ -57,7 +57,7 @@ namespace BDInfo
|
||||
#endif
|
||||
Streams.Clear();
|
||||
|
||||
fileStream = _fileSystem.OpenRead(FileInfo.FullName);
|
||||
fileStream = File.OpenRead(FileInfo.FullName);
|
||||
fileReader = new BinaryReader(fileStream);
|
||||
|
||||
byte[] data = new byte[fileStream.Length];
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
- [cvium](https://github.com/cvium)
|
||||
- [wtayl0r](https://github.com/wtayl0r)
|
||||
- [TtheCreator](https://github.com/Tthecreator)
|
||||
- [dkanada](https://github.com/dkanada)
|
||||
- [LogicalPhallacy](https://github.com/LogicalPhallacy/)
|
||||
- [RazeLighter777](https://github.com/RazeLighter777)
|
||||
- [WillWill56](https://github.com/WillWill56)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
||||
28
Dockerfile
28
Dockerfile
@@ -1,32 +1,16 @@
|
||||
ARG DOTNET_VERSION=2
|
||||
|
||||
|
||||
# Download ffmpeg first to allow quicker rebuild of other layers
|
||||
FROM alpine as ffmpeg
|
||||
ARG FFMPEG_URL=https://www.johnvansickle.com/ffmpeg/old-releases/ffmpeg-4.0.3-64bit-static.tar.xz
|
||||
RUN wget ${FFMPEG_URL} -O - | tar Jxf - \
|
||||
&& mkdir ffmpeg-bin \
|
||||
&& mv ffmpeg*/ffmpeg ffmpeg-bin \
|
||||
&& mv ffmpeg*/ffprobe ffmpeg-bin
|
||||
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \
|
||||
&& dotnet clean \
|
||||
&& dotnet publish \
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
RUN dotnet publish \
|
||||
--configuration release \
|
||||
--output /jellyfin \
|
||||
Jellyfin.Server
|
||||
|
||||
|
||||
FROM jrottenberg/ffmpeg:4.0-vaapi as ffmpeg
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=ffmpeg /ffmpeg-bin/* /usr/bin/
|
||||
EXPOSE 8096
|
||||
VOLUME /config /media
|
||||
|
||||
# libfontconfig1 is required for Skia
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
@@ -34,4 +18,8 @@ RUN apt-get update \
|
||||
&& apt-get clean autoclean \
|
||||
&& apt-get autoremove \
|
||||
&& rm -rf /var/lib/{apt,dpkg,cache,log}
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll -programdata /config
|
||||
COPY --from=ffmpeg / /
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
EXPOSE 8096
|
||||
VOLUME /config /media
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config
|
||||
|
||||
@@ -1,24 +1,34 @@
|
||||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=3.0
|
||||
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch-arm32v7 as builder
|
||||
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
||||
FROM alpine as qemu_extract
|
||||
COPY --from=qemu /usr/bin qemu-arm-static.tar.gz
|
||||
RUN tar -xzvf qemu-arm-static.tar.gz
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
#TODO Remove or update the sed line when we update dotnet version.
|
||||
RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \
|
||||
&& find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \; \
|
||||
&& dotnet clean -maxcpucount:1 \
|
||||
&& dotnet publish \
|
||||
-maxcpucount:1 \
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
# TODO Remove or update the sed line when we update dotnet version.
|
||||
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
|
||||
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-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
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
EXPOSE 8096
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y ffmpeg
|
||||
VOLUME /config /media
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll -programdata /config
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config
|
||||
|
||||
@@ -1,33 +1,35 @@
|
||||
# Requires binfm_misc registration for aarch64
|
||||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=3.0
|
||||
|
||||
|
||||
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
||||
FROM alpine as qemu_extract
|
||||
COPY --from=qemu /usr/bin qemu_user_static.tgz
|
||||
RUN tar -xzvf qemu_user_static.tgz
|
||||
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-arm64v8 as builder
|
||||
COPY --from=qemu_extract qemu-* /usr/bin
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
#TODO Remove or update the sed line when we update dotnet version.
|
||||
RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \
|
||||
&& find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \; \
|
||||
&& dotnet clean \
|
||||
&& dotnet publish \
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
# TODO Remove or update the sed line when we update dotnet version.
|
||||
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
|
||||
|
||||
|
||||
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm64v8
|
||||
COPY --from=qemu_extract qemu-* /usr/bin
|
||||
COPY --from=qemu_extract qemu-aarch64-static /usr/bin
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
EXPOSE 8096
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y ffmpeg
|
||||
VOLUME /config /media
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll -programdata /config
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config
|
||||
|
||||
@@ -236,7 +236,9 @@ namespace Emby.Dlna.Api
|
||||
|
||||
public object Get(GetIcon request)
|
||||
{
|
||||
var contentType = "image/" + Path.GetExtension(request.Filename).TrimStart('.').ToLower();
|
||||
var contentType = "image/" + Path.GetExtension(request.Filename)
|
||||
.TrimStart('.')
|
||||
.ToLowerInvariant();
|
||||
|
||||
var cacheLength = TimeSpan.FromDays(365);
|
||||
var cacheKey = Request.RawUrl.GetMD5();
|
||||
|
||||
@@ -76,7 +76,6 @@ namespace Emby.Dlna.ContentDirectory
|
||||
_dlna.GetDefaultProfile();
|
||||
|
||||
var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
|
||||
string accessToken = null;
|
||||
|
||||
var user = GetUser(profile);
|
||||
|
||||
@@ -85,7 +84,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
_libraryManager,
|
||||
profile,
|
||||
serverAddress,
|
||||
accessToken,
|
||||
null,
|
||||
_imageProcessor,
|
||||
_userDataManager,
|
||||
user,
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
_profile = profile;
|
||||
_config = config;
|
||||
|
||||
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, _logger, libraryManager, mediaEncoder);
|
||||
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, _logger, mediaEncoder);
|
||||
}
|
||||
|
||||
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
|
||||
@@ -454,7 +454,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
User = user,
|
||||
Recursive = true,
|
||||
IsMissing = false,
|
||||
ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
|
||||
ExcludeItemTypes = new[] { typeof(Book).Name },
|
||||
IsFolder = isFolder,
|
||||
MediaTypes = mediaTypes.ToArray(),
|
||||
DtoOptions = GetDtoOptions()
|
||||
@@ -483,27 +483,26 @@ namespace Emby.Dlna.ContentDirectory
|
||||
return GetGenreItems(item, Guid.Empty, user, sort, startIndex, limit);
|
||||
}
|
||||
|
||||
if (!stubType.HasValue || stubType.Value != StubType.Folder)
|
||||
if ((!stubType.HasValue || stubType.Value != StubType.Folder)
|
||||
&& item is IHasCollectionType collectionFolder)
|
||||
{
|
||||
var collectionFolder = item as IHasCollectionType;
|
||||
if (collectionFolder != null && string.Equals(CollectionType.Music, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(CollectionType.Music, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetMusicFolders(item, user, stubType, sort, startIndex, limit);
|
||||
}
|
||||
if (collectionFolder != null && string.Equals(CollectionType.Movies, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||
else if (string.Equals(CollectionType.Movies, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetMovieFolders(item, user, stubType, sort, startIndex, limit);
|
||||
}
|
||||
if (collectionFolder != null && string.Equals(CollectionType.TvShows, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||
else if (string.Equals(CollectionType.TvShows, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetTvFolders(item, user, stubType, sort, startIndex, limit);
|
||||
}
|
||||
|
||||
if (collectionFolder != null && string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||
else if (string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetFolders(item, user, stubType, sort, startIndex, limit);
|
||||
}
|
||||
if (collectionFolder != null && string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||
else if (string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetLiveTvChannels(item, user, stubType, sort, startIndex, limit);
|
||||
}
|
||||
@@ -524,7 +523,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
Limit = limit,
|
||||
StartIndex = startIndex,
|
||||
IsVirtualItem = false,
|
||||
ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
|
||||
ExcludeItemTypes = new[] { typeof(Book).Name },
|
||||
IsPlaceHolder = false,
|
||||
DtoOptions = GetDtoOptions()
|
||||
};
|
||||
|
||||
@@ -43,22 +43,30 @@ namespace Emby.Dlna.Didl
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
|
||||
public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress, string accessToken, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, ILogger logger, ILibraryManager libraryManager, IMediaEncoder mediaEncoder)
|
||||
public DidlBuilder(
|
||||
DeviceProfile profile,
|
||||
User user,
|
||||
IImageProcessor imageProcessor,
|
||||
string serverAddress,
|
||||
string accessToken,
|
||||
IUserDataManager userDataManager,
|
||||
ILocalizationManager localization,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
ILogger logger,
|
||||
IMediaEncoder mediaEncoder)
|
||||
{
|
||||
_profile = profile;
|
||||
_user = user;
|
||||
_imageProcessor = imageProcessor;
|
||||
_serverAddress = serverAddress;
|
||||
_accessToken = accessToken;
|
||||
_userDataManager = userDataManager;
|
||||
_localization = localization;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_logger = logger;
|
||||
_libraryManager = libraryManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_accessToken = accessToken;
|
||||
_user = user;
|
||||
}
|
||||
|
||||
public static string NormalizeDlnaMediaUrl(string url)
|
||||
@@ -117,7 +125,8 @@ namespace Emby.Dlna.Didl
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteItemElement(DlnaOptions options,
|
||||
public void WriteItemElement(
|
||||
DlnaOptions options,
|
||||
XmlWriter writer,
|
||||
BaseItem item,
|
||||
User user,
|
||||
@@ -232,12 +241,15 @@ namespace Emby.Dlna.Didl
|
||||
AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo);
|
||||
}
|
||||
|
||||
var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken)
|
||||
.Where(subtitle => subtitle.DeliveryMethod == SubtitleDeliveryMethod.External)
|
||||
.ToList();
|
||||
var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken);
|
||||
|
||||
foreach (var subtitle in subtitleProfiles)
|
||||
{
|
||||
if (subtitle.DeliveryMethod != SubtitleDeliveryMethod.External)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var subtitleAdded = AddSubtitleElement(writer, subtitle);
|
||||
|
||||
if (subtitleAdded && _profile.EnableSingleSubtitleLimit)
|
||||
@@ -250,7 +262,8 @@ namespace Emby.Dlna.Didl
|
||||
private bool AddSubtitleElement(XmlWriter writer, SubtitleStreamInfo info)
|
||||
{
|
||||
var subtitleProfile = _profile.SubtitleProfiles
|
||||
.FirstOrDefault(i => string.Equals(info.Format, i.Format, StringComparison.OrdinalIgnoreCase) && i.Method == SubtitleDeliveryMethod.External);
|
||||
.FirstOrDefault(i => string.Equals(info.Format, i.Format, StringComparison.OrdinalIgnoreCase)
|
||||
&& i.Method == SubtitleDeliveryMethod.External);
|
||||
|
||||
if (subtitleProfile == null)
|
||||
{
|
||||
@@ -265,7 +278,7 @@ namespace Emby.Dlna.Didl
|
||||
// <sec:CaptionInfo sec:type="srt">http://192.168.1.3:9999/video.srt</sec:CaptionInfo>
|
||||
|
||||
writer.WriteStartElement("sec", "CaptionInfoEx", null);
|
||||
writer.WriteAttributeString("sec", "type", null, info.Format.ToLower());
|
||||
writer.WriteAttributeString("sec", "type", null, info.Format.ToLowerInvariant());
|
||||
|
||||
writer.WriteString(info.Url);
|
||||
writer.WriteFullEndElement();
|
||||
@@ -282,7 +295,7 @@ namespace Emby.Dlna.Didl
|
||||
else
|
||||
{
|
||||
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
||||
var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLower());
|
||||
var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLowerInvariant());
|
||||
writer.WriteAttributeString("protocolInfo", protocolInfo);
|
||||
|
||||
writer.WriteString(info.Url);
|
||||
@@ -387,91 +400,39 @@ namespace Emby.Dlna.Didl
|
||||
|
||||
private string GetDisplayName(BaseItem item, StubType? itemStubType, BaseItem context)
|
||||
{
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.Latest)
|
||||
if (itemStubType.HasValue)
|
||||
{
|
||||
return _localization.GetLocalizedString("Latest");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.Playlists)
|
||||
{
|
||||
return _localization.GetLocalizedString("Playlists");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.AlbumArtists)
|
||||
{
|
||||
return _localization.GetLocalizedString("HeaderAlbumArtists");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.Albums)
|
||||
{
|
||||
return _localization.GetLocalizedString("Albums");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.Artists)
|
||||
{
|
||||
return _localization.GetLocalizedString("Artists");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.Songs)
|
||||
{
|
||||
return _localization.GetLocalizedString("Songs");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.Genres)
|
||||
{
|
||||
return _localization.GetLocalizedString("Genres");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteAlbums)
|
||||
{
|
||||
return _localization.GetLocalizedString("HeaderFavoriteAlbums");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteArtists)
|
||||
{
|
||||
return _localization.GetLocalizedString("HeaderFavoriteArtists");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteSongs)
|
||||
{
|
||||
return _localization.GetLocalizedString("HeaderFavoriteSongs");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.ContinueWatching)
|
||||
{
|
||||
return _localization.GetLocalizedString("HeaderContinueWatching");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.Movies)
|
||||
{
|
||||
return _localization.GetLocalizedString("Movies");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.Collections)
|
||||
{
|
||||
return _localization.GetLocalizedString("Collections");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.Favorites)
|
||||
{
|
||||
return _localization.GetLocalizedString("Favorites");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.NextUp)
|
||||
{
|
||||
return _localization.GetLocalizedString("HeaderNextUp");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteSeries)
|
||||
{
|
||||
return _localization.GetLocalizedString("HeaderFavoriteShows");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteEpisodes)
|
||||
{
|
||||
return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
|
||||
}
|
||||
if (itemStubType.HasValue && itemStubType.Value == StubType.Series)
|
||||
{
|
||||
return _localization.GetLocalizedString("Shows");
|
||||
switch (itemStubType.Value)
|
||||
{
|
||||
case StubType.Latest: return _localization.GetLocalizedString("Latest");
|
||||
case StubType.Playlists: return _localization.GetLocalizedString("Playlists");
|
||||
case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists");
|
||||
case StubType.Albums: return _localization.GetLocalizedString("Albums");
|
||||
case StubType.Artists: return _localization.GetLocalizedString("Artists");
|
||||
case StubType.Songs: return _localization.GetLocalizedString("Songs");
|
||||
case StubType.Genres: return _localization.GetLocalizedString("Genres");
|
||||
case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums");
|
||||
case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists");
|
||||
case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs");
|
||||
case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching");
|
||||
case StubType.Movies: return _localization.GetLocalizedString("Movies");
|
||||
case StubType.Collections: return _localization.GetLocalizedString("Collections");
|
||||
case StubType.Favorites: return _localization.GetLocalizedString("Favorites");
|
||||
case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp");
|
||||
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
|
||||
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
|
||||
case StubType.Series: return _localization.GetLocalizedString("Shows");
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
var episode = item as Episode;
|
||||
var season = context as Season;
|
||||
|
||||
if (episode != null && season != null)
|
||||
if (item is Episode episode && context is Season season)
|
||||
{
|
||||
// This is a special embedded within a season
|
||||
if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0)
|
||||
if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0
|
||||
&& season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
|
||||
{
|
||||
if (season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
|
||||
{
|
||||
return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name);
|
||||
}
|
||||
return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name);
|
||||
}
|
||||
|
||||
if (item.IndexNumber.HasValue)
|
||||
@@ -585,10 +546,8 @@ namespace Emby.Dlna.Didl
|
||||
|
||||
public static bool IsIdRoot(string id)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(id) ||
|
||||
|
||||
string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
|
||||
|
||||
if (string.IsNullOrWhiteSpace(id)
|
||||
|| string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
|
||||
// Samsung sometimes uses 1 as root
|
||||
|| string.Equals(id, "1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -808,7 +767,7 @@ namespace Emby.Dlna.Didl
|
||||
{
|
||||
writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre.musicGenre");
|
||||
}
|
||||
else if (item is Genre || item is GameGenre)
|
||||
else if (item is Genre)
|
||||
{
|
||||
writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre");
|
||||
}
|
||||
@@ -844,7 +803,7 @@ namespace Emby.Dlna.Didl
|
||||
// var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
|
||||
// ?? PersonType.Actor;
|
||||
|
||||
// AddValue(writer, "upnp", type.ToLower(), actor.Name, NS_UPNP);
|
||||
// AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
|
||||
|
||||
// index++;
|
||||
|
||||
@@ -932,7 +891,7 @@ namespace Emby.Dlna.Didl
|
||||
|
||||
private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
|
||||
{
|
||||
ImageDownloadInfo imageInfo = GetImageInfo(item);;
|
||||
ImageDownloadInfo imageInfo = GetImageInfo(item);
|
||||
|
||||
if (imageInfo == null)
|
||||
{
|
||||
@@ -1088,8 +1047,8 @@ namespace Emby.Dlna.Didl
|
||||
//{
|
||||
// var size = _imageProcessor.GetImageSize(imageInfo);
|
||||
|
||||
// width = Convert.ToInt32(size.Width);
|
||||
// height = Convert.ToInt32(size.Height);
|
||||
// width = size.Width;
|
||||
// height = size.Height;
|
||||
//}
|
||||
//catch
|
||||
//{
|
||||
@@ -1112,7 +1071,7 @@ namespace Emby.Dlna.Didl
|
||||
};
|
||||
}
|
||||
|
||||
class ImageDownloadInfo
|
||||
private class ImageDownloadInfo
|
||||
{
|
||||
internal Guid ItemId;
|
||||
internal string ImageTag;
|
||||
@@ -1128,7 +1087,7 @@ namespace Emby.Dlna.Didl
|
||||
internal ItemImageInfo ItemImageInfo;
|
||||
}
|
||||
|
||||
class ImageUrlInfo
|
||||
private class ImageUrlInfo
|
||||
{
|
||||
internal string Url;
|
||||
|
||||
@@ -1147,7 +1106,7 @@ namespace Emby.Dlna.Didl
|
||||
|
||||
if (stubType.HasValue)
|
||||
{
|
||||
id = stubType.Value.ToString().ToLower() + "_" + id;
|
||||
id = stubType.Value.ToString().ToLowerInvariant() + "_" + id;
|
||||
}
|
||||
|
||||
return id;
|
||||
@@ -1162,8 +1121,7 @@ namespace Emby.Dlna.Didl
|
||||
info.ImageTag,
|
||||
format,
|
||||
maxWidth.ToString(CultureInfo.InvariantCulture),
|
||||
maxHeight.ToString(CultureInfo.InvariantCulture)
|
||||
);
|
||||
maxHeight.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
var width = info.Width;
|
||||
var height = info.Height;
|
||||
@@ -1172,15 +1130,11 @@ namespace Emby.Dlna.Didl
|
||||
|
||||
if (width.HasValue && height.HasValue)
|
||||
{
|
||||
var newSize = DrawingUtils.Resize(new ImageSize
|
||||
{
|
||||
Height = height.Value,
|
||||
Width = width.Value
|
||||
var newSize = DrawingUtils.Resize(
|
||||
new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
|
||||
|
||||
}, 0, 0, maxWidth, maxHeight);
|
||||
|
||||
width = Convert.ToInt32(newSize.Width);
|
||||
height = Convert.ToInt32(newSize.Height);
|
||||
width = newSize.Width;
|
||||
height = newSize.Height;
|
||||
|
||||
var normalizedFormat = format
|
||||
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.Profiles;
|
||||
using Emby.Dlna.Server;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
@@ -48,11 +49,11 @@ namespace Emby.Dlna
|
||||
_assemblyInfo = assemblyInfo;
|
||||
}
|
||||
|
||||
public void InitProfiles()
|
||||
public async Task InitProfilesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
ExtractSystemProfiles();
|
||||
await ExtractSystemProfilesAsync();
|
||||
LoadProfiles();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -300,7 +301,7 @@ namespace Emby.Dlna
|
||||
|
||||
profile = ReserializeProfile(tempProfile);
|
||||
|
||||
profile.Id = path.ToLower().GetMD5().ToString("N");
|
||||
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N");
|
||||
|
||||
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
|
||||
|
||||
@@ -352,14 +353,14 @@ namespace Emby.Dlna
|
||||
|
||||
Info = new DeviceProfileInfo
|
||||
{
|
||||
Id = file.FullName.ToLower().GetMD5().ToString("N"),
|
||||
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N"),
|
||||
Name = _fileSystem.GetFileNameWithoutExtension(file),
|
||||
Type = type
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void ExtractSystemProfiles()
|
||||
private async Task ExtractSystemProfilesAsync()
|
||||
{
|
||||
var namespaceName = GetType().Namespace + ".Profiles.Xml.";
|
||||
|
||||
@@ -379,18 +380,18 @@ namespace Emby.Dlna
|
||||
|
||||
if (!fileInfo.Exists || fileInfo.Length != stream.Length)
|
||||
{
|
||||
_fileSystem.CreateDirectory(systemProfilesPath);
|
||||
Directory.CreateDirectory(systemProfilesPath);
|
||||
|
||||
using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
||||
{
|
||||
stream.CopyTo(fileStream);
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not necessary, but just to make it easy to find
|
||||
_fileSystem.CreateDirectory(UserProfilesPath);
|
||||
Directory.CreateDirectory(UserProfilesPath);
|
||||
}
|
||||
|
||||
public void DeleteProfile(string id)
|
||||
@@ -506,7 +507,7 @@ namespace Emby.Dlna
|
||||
? ImageFormat.Png
|
||||
: ImageFormat.Jpg;
|
||||
|
||||
var resource = GetType().Namespace + ".Images." + filename.ToLower();
|
||||
var resource = GetType().Namespace + ".Images." + filename.ToLowerInvariant();
|
||||
|
||||
return new ImageStream
|
||||
{
|
||||
|
||||
@@ -20,7 +20,6 @@ using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.System;
|
||||
using MediaBrowser.Model.Threading;
|
||||
using MediaBrowser.Model.Xml;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Rssdp;
|
||||
@@ -49,8 +48,7 @@ namespace Emby.Dlna.Main
|
||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||
|
||||
private SsdpDevicePublisher _Publisher;
|
||||
|
||||
private readonly ITimerFactory _timerFactory;
|
||||
|
||||
private readonly ISocketFactory _socketFactory;
|
||||
private readonly IEnvironmentInfo _environmentInfo;
|
||||
private readonly INetworkManager _networkManager;
|
||||
@@ -78,7 +76,6 @@ namespace Emby.Dlna.Main
|
||||
IDeviceDiscovery deviceDiscovery,
|
||||
IMediaEncoder mediaEncoder,
|
||||
ISocketFactory socketFactory,
|
||||
ITimerFactory timerFactory,
|
||||
IEnvironmentInfo environmentInfo,
|
||||
INetworkManager networkManager,
|
||||
IUserViewManager userViewManager,
|
||||
@@ -99,7 +96,6 @@ namespace Emby.Dlna.Main
|
||||
_deviceDiscovery = deviceDiscovery;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_socketFactory = socketFactory;
|
||||
_timerFactory = timerFactory;
|
||||
_environmentInfo = environmentInfo;
|
||||
_networkManager = networkManager;
|
||||
_logger = loggerFactory.CreateLogger("Dlna");
|
||||
@@ -125,9 +121,9 @@ namespace Emby.Dlna.Main
|
||||
Current = this;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
public async Task RunAsync()
|
||||
{
|
||||
((DlnaManager)_dlnaManager).InitProfiles();
|
||||
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
|
||||
|
||||
ReloadComponents();
|
||||
|
||||
@@ -233,7 +229,7 @@ namespace Emby.Dlna.Main
|
||||
|
||||
try
|
||||
{
|
||||
_Publisher = new SsdpDevicePublisher(_communicationsServer, _timerFactory, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion);
|
||||
_Publisher = new SsdpDevicePublisher(_communicationsServer, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion);
|
||||
_Publisher.LogFunction = LogMessage;
|
||||
_Publisher.SupportPnpRootDevice = false;
|
||||
|
||||
@@ -263,7 +259,7 @@ namespace Emby.Dlna.Main
|
||||
|
||||
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
|
||||
|
||||
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address.ToString());
|
||||
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
|
||||
|
||||
var descriptorUri = "/dlna/" + udn + "/description.xml";
|
||||
var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorUri);
|
||||
@@ -353,8 +349,7 @@ namespace Emby.Dlna.Main
|
||||
_userDataManager,
|
||||
_localization,
|
||||
_mediaSourceManager,
|
||||
_mediaEncoder,
|
||||
_timerFactory);
|
||||
_mediaEncoder);
|
||||
|
||||
_manager.Start();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ using Emby.Dlna.Server;
|
||||
using Emby.Dlna.Ssdp;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
@@ -19,7 +18,7 @@ namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
#region Fields & Properties
|
||||
|
||||
private ITimer _timer;
|
||||
private Timer _timer;
|
||||
|
||||
public DeviceInfo Properties { get; set; }
|
||||
|
||||
@@ -40,12 +39,7 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
public TimeSpan? Duration { get; set; }
|
||||
|
||||
private TimeSpan _position = TimeSpan.FromSeconds(0);
|
||||
public TimeSpan Position
|
||||
{
|
||||
get => _position;
|
||||
set => _position = value;
|
||||
}
|
||||
public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(0);
|
||||
|
||||
public TRANSPORTSTATE TransportState { get; private set; }
|
||||
|
||||
@@ -61,24 +55,20 @@ namespace Emby.Dlna.PlayTo
|
||||
private readonly ILogger _logger;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
public DateTime DateLastActivity { get; private set; }
|
||||
public Action OnDeviceUnavailable { get; set; }
|
||||
|
||||
private readonly ITimerFactory _timerFactory;
|
||||
|
||||
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config, ITimerFactory timerFactory)
|
||||
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config)
|
||||
{
|
||||
Properties = deviceProperties;
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_timerFactory = timerFactory;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_logger.LogDebug("Dlna Device.Start");
|
||||
_timer = _timerFactory.Create(TimerCallback, null, 1000, Timeout.Infinite);
|
||||
_timer = new Timer(TimerCallback, null, 1000, Timeout.Infinite);
|
||||
}
|
||||
|
||||
private DateTime _lastVolumeRefresh;
|
||||
@@ -119,7 +109,9 @@ namespace Emby.Dlna.PlayTo
|
||||
lock (_timerLock)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_volumeRefreshActive = true;
|
||||
|
||||
@@ -136,7 +128,9 @@ namespace Emby.Dlna.PlayTo
|
||||
lock (_timerLock)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_volumeRefreshActive = false;
|
||||
|
||||
@@ -144,11 +138,6 @@ namespace Emby.Dlna.PlayTo
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPlaybackStartedExternally()
|
||||
{
|
||||
RestartTimer(true);
|
||||
}
|
||||
|
||||
#region Commanding
|
||||
|
||||
public Task VolumeDown(CancellationToken cancellationToken)
|
||||
@@ -333,7 +322,9 @@ namespace Emby.Dlna.PlayTo
|
||||
private string CreateDidlMeta(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return DescriptionXmlBuilder.Escape(value);
|
||||
}
|
||||
@@ -342,10 +333,11 @@ namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play");
|
||||
if (command == null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var service = GetAvTransportService();
|
||||
|
||||
if (service == null)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to find service");
|
||||
@@ -369,7 +361,9 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
|
||||
if (command == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var service = GetAvTransportService();
|
||||
|
||||
@@ -385,7 +379,9 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
|
||||
if (command == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var service = GetAvTransportService();
|
||||
|
||||
@@ -405,7 +401,9 @@ namespace Emby.Dlna.PlayTo
|
||||
private async void TimerCallback(object sender)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -425,8 +423,6 @@ namespace Emby.Dlna.PlayTo
|
||||
return;
|
||||
}
|
||||
|
||||
DateLastActivity = DateTime.UtcNow;
|
||||
|
||||
if (transportState.HasValue)
|
||||
{
|
||||
// If we're not playing anything no need to get additional data
|
||||
@@ -505,7 +501,9 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
|
||||
if (command == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var service = GetServiceRenderingControl();
|
||||
|
||||
@@ -518,13 +516,17 @@ namespace Emby.Dlna.PlayTo
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (result == null || result.Document == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null);
|
||||
var volumeValue = volume == null ? null : volume.Value;
|
||||
var volumeValue = volume?.Value;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(volumeValue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Volume = int.Parse(volumeValue, UsCulture);
|
||||
|
||||
@@ -545,7 +547,9 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
|
||||
if (command == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var service = GetServiceRenderingControl();
|
||||
|
||||
@@ -560,39 +564,44 @@ namespace Emby.Dlna.PlayTo
|
||||
if (result == null || result.Document == null)
|
||||
return;
|
||||
|
||||
var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse").Select(i => i.Element("CurrentMute")).FirstOrDefault(i => i != null);
|
||||
var value = valueNode == null ? null : valueNode.Value;
|
||||
var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse")
|
||||
.Select(i => i.Element("CurrentMute"))
|
||||
.FirstOrDefault(i => i != null);
|
||||
|
||||
IsMuted = string.Equals(value, "1", StringComparison.OrdinalIgnoreCase);
|
||||
IsMuted = string.Equals(valueNode?.Value, "1", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private async Task<TRANSPORTSTATE?> GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
||||
{
|
||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
|
||||
if (command == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var service = GetAvTransportService();
|
||||
if (service == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType), false)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (result == null || result.Document == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var transportState =
|
||||
result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
|
||||
|
||||
var transportStateValue = transportState == null ? null : transportState.Value;
|
||||
|
||||
if (transportStateValue != null)
|
||||
if (transportStateValue != null
|
||||
&& Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state))
|
||||
{
|
||||
if (Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state))
|
||||
{
|
||||
return state;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -602,10 +611,11 @@ namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo");
|
||||
if (command == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var service = GetAvTransportService();
|
||||
|
||||
if (service == null)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to find service");
|
||||
@@ -617,7 +627,9 @@ namespace Emby.Dlna.PlayTo
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (result == null || result.Document == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault();
|
||||
|
||||
@@ -657,11 +669,13 @@ namespace Emby.Dlna.PlayTo
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<Tuple<bool, uBaseObject>> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
||||
private async Task<(bool, uBaseObject)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
||||
{
|
||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
|
||||
if (command == null)
|
||||
return new Tuple<bool, uBaseObject>(false, null);
|
||||
{
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
var service = GetAvTransportService();
|
||||
|
||||
@@ -676,7 +690,9 @@ namespace Emby.Dlna.PlayTo
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (result == null || result.Document == null)
|
||||
return new Tuple<bool, uBaseObject>(false, null);
|
||||
{
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
var trackUriElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i != null);
|
||||
var trackUri = trackUriElem == null ? null : trackUriElem.Value;
|
||||
@@ -684,8 +700,8 @@ namespace Emby.Dlna.PlayTo
|
||||
var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
|
||||
var duration = durationElem == null ? null : durationElem.Value;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(duration) &&
|
||||
!string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
|
||||
if (!string.IsNullOrWhiteSpace(duration)
|
||||
&& !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Duration = TimeSpan.Parse(duration, UsCulture);
|
||||
}
|
||||
@@ -707,14 +723,14 @@ namespace Emby.Dlna.PlayTo
|
||||
if (track == null)
|
||||
{
|
||||
//If track is null, some vendors do this, use GetMediaInfo instead
|
||||
return new Tuple<bool, uBaseObject>(true, null);
|
||||
return (true, null);
|
||||
}
|
||||
|
||||
var trackString = (string)track;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(trackString) || string.Equals(trackString, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new Tuple<bool, uBaseObject>(true, null);
|
||||
return (true, null);
|
||||
}
|
||||
|
||||
XElement uPnpResponse;
|
||||
@@ -735,7 +751,7 @@ namespace Emby.Dlna.PlayTo
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unable to parse xml {0}", trackString);
|
||||
return new Tuple<bool, uBaseObject>(true, null);
|
||||
return (true, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -743,7 +759,7 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
var uTrack = CreateUBaseObject(e, trackUri);
|
||||
|
||||
return new Tuple<bool, uBaseObject>(true, uTrack);
|
||||
return (true, uTrack);
|
||||
}
|
||||
|
||||
private static uBaseObject CreateUBaseObject(XElement container, string trackUri)
|
||||
@@ -801,11 +817,9 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
private async Task<TransportCommands> GetAVProtocolAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var avCommands = AvCommands;
|
||||
|
||||
if (avCommands != null)
|
||||
if (AvCommands != null)
|
||||
{
|
||||
return avCommands;
|
||||
return AvCommands;
|
||||
}
|
||||
|
||||
if (_disposed)
|
||||
@@ -825,18 +839,15 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
avCommands = TransportCommands.Create(document);
|
||||
AvCommands = avCommands;
|
||||
return avCommands;
|
||||
AvCommands = TransportCommands.Create(document);
|
||||
return AvCommands;
|
||||
}
|
||||
|
||||
private async Task<TransportCommands> GetRenderingProtocolAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var rendererCommands = RendererCommands;
|
||||
|
||||
if (rendererCommands != null)
|
||||
if (RendererCommands != null)
|
||||
{
|
||||
return rendererCommands;
|
||||
return RendererCommands;
|
||||
}
|
||||
|
||||
if (_disposed)
|
||||
@@ -845,7 +856,6 @@ namespace Emby.Dlna.PlayTo
|
||||
}
|
||||
|
||||
var avService = GetServiceRenderingControl();
|
||||
|
||||
if (avService == null)
|
||||
{
|
||||
throw new ArgumentException("Device AvService is null");
|
||||
@@ -857,9 +867,8 @@ namespace Emby.Dlna.PlayTo
|
||||
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
|
||||
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
rendererCommands = TransportCommands.Create(document);
|
||||
RendererCommands = rendererCommands;
|
||||
return rendererCommands;
|
||||
RendererCommands = TransportCommands.Create(document);
|
||||
return RendererCommands;
|
||||
}
|
||||
|
||||
private string NormalizeUrl(string baseUrl, string url)
|
||||
@@ -871,85 +880,103 @@ namespace Emby.Dlna.PlayTo
|
||||
}
|
||||
|
||||
if (!url.Contains("/"))
|
||||
{
|
||||
url = "/dmr/" + url;
|
||||
}
|
||||
|
||||
if (!url.StartsWith("/"))
|
||||
{
|
||||
url = "/" + url;
|
||||
}
|
||||
|
||||
return baseUrl + url;
|
||||
}
|
||||
|
||||
private TransportCommands AvCommands
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
private TransportCommands AvCommands { get; set; }
|
||||
|
||||
private TransportCommands RendererCommands
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
private TransportCommands RendererCommands { get; set; }
|
||||
|
||||
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, ITimerFactory timerFactory, CancellationToken cancellationToken)
|
||||
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, CancellationToken cancellationToken)
|
||||
{
|
||||
var ssdpHttpClient = new SsdpHttpClient(httpClient, config);
|
||||
|
||||
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var deviceProperties = new DeviceInfo();
|
||||
|
||||
var friendlyNames = new List<string>();
|
||||
|
||||
var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault();
|
||||
if (name != null && !string.IsNullOrWhiteSpace(name.Value))
|
||||
{
|
||||
friendlyNames.Add(name.Value);
|
||||
}
|
||||
|
||||
var room = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault();
|
||||
if (room != null && !string.IsNullOrWhiteSpace(room.Value))
|
||||
{
|
||||
friendlyNames.Add(room.Value);
|
||||
}
|
||||
|
||||
deviceProperties.Name = string.Join(" ", friendlyNames.ToArray());
|
||||
var deviceProperties = new DeviceInfo()
|
||||
{
|
||||
Name = string.Join(" ", friendlyNames),
|
||||
BaseUrl = string.Format("http://{0}:{1}", url.Host, url.Port)
|
||||
};
|
||||
|
||||
var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault();
|
||||
if (model != null)
|
||||
{
|
||||
deviceProperties.ModelName = model.Value;
|
||||
}
|
||||
|
||||
var modelNumber = document.Descendants(uPnpNamespaces.ud.GetName("modelNumber")).FirstOrDefault();
|
||||
if (modelNumber != null)
|
||||
{
|
||||
deviceProperties.ModelNumber = modelNumber.Value;
|
||||
}
|
||||
|
||||
var uuid = document.Descendants(uPnpNamespaces.ud.GetName("UDN")).FirstOrDefault();
|
||||
if (uuid != null)
|
||||
{
|
||||
deviceProperties.UUID = uuid.Value;
|
||||
}
|
||||
|
||||
var manufacturer = document.Descendants(uPnpNamespaces.ud.GetName("manufacturer")).FirstOrDefault();
|
||||
if (manufacturer != null)
|
||||
{
|
||||
deviceProperties.Manufacturer = manufacturer.Value;
|
||||
}
|
||||
|
||||
var manufacturerUrl = document.Descendants(uPnpNamespaces.ud.GetName("manufacturerURL")).FirstOrDefault();
|
||||
if (manufacturerUrl != null)
|
||||
{
|
||||
deviceProperties.ManufacturerUrl = manufacturerUrl.Value;
|
||||
}
|
||||
|
||||
var presentationUrl = document.Descendants(uPnpNamespaces.ud.GetName("presentationURL")).FirstOrDefault();
|
||||
if (presentationUrl != null)
|
||||
{
|
||||
deviceProperties.PresentationUrl = presentationUrl.Value;
|
||||
}
|
||||
|
||||
var modelUrl = document.Descendants(uPnpNamespaces.ud.GetName("modelURL")).FirstOrDefault();
|
||||
if (modelUrl != null)
|
||||
{
|
||||
deviceProperties.ModelUrl = modelUrl.Value;
|
||||
}
|
||||
|
||||
var serialNumber = document.Descendants(uPnpNamespaces.ud.GetName("serialNumber")).FirstOrDefault();
|
||||
if (serialNumber != null)
|
||||
{
|
||||
deviceProperties.SerialNumber = serialNumber.Value;
|
||||
}
|
||||
|
||||
var modelDescription = document.Descendants(uPnpNamespaces.ud.GetName("modelDescription")).FirstOrDefault();
|
||||
if (modelDescription != null)
|
||||
{
|
||||
deviceProperties.ModelDescription = modelDescription.Value;
|
||||
|
||||
deviceProperties.BaseUrl = string.Format("http://{0}:{1}", url.Host, url.Port);
|
||||
}
|
||||
|
||||
var icon = document.Descendants(uPnpNamespaces.ud.GetName("icon")).FirstOrDefault();
|
||||
|
||||
if (icon != null)
|
||||
{
|
||||
deviceProperties.Icon = CreateIcon(icon);
|
||||
@@ -958,12 +985,15 @@ namespace Emby.Dlna.PlayTo
|
||||
foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList")))
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service"));
|
||||
|
||||
if (servicesList == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var element in servicesList)
|
||||
{
|
||||
@@ -976,9 +1006,7 @@ namespace Emby.Dlna.PlayTo
|
||||
}
|
||||
}
|
||||
|
||||
var device = new Device(deviceProperties, httpClient, logger, config, timerFactory);
|
||||
|
||||
return device;
|
||||
return new Device(deviceProperties, httpClient, logger, config);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -1065,13 +1093,10 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
private void OnPlaybackStart(uBaseObject mediaInfo)
|
||||
{
|
||||
if (PlaybackStart != null)
|
||||
PlaybackStart?.Invoke(this, new PlaybackStartEventArgs
|
||||
{
|
||||
PlaybackStart.Invoke(this, new PlaybackStartEventArgs
|
||||
{
|
||||
MediaInfo = mediaInfo
|
||||
});
|
||||
}
|
||||
MediaInfo = mediaInfo
|
||||
});
|
||||
}
|
||||
|
||||
private void OnPlaybackProgress(uBaseObject mediaInfo)
|
||||
@@ -1082,58 +1107,56 @@ namespace Emby.Dlna.PlayTo
|
||||
return;
|
||||
}
|
||||
|
||||
if (PlaybackProgress != null)
|
||||
PlaybackProgress?.Invoke(this, new PlaybackProgressEventArgs
|
||||
{
|
||||
PlaybackProgress.Invoke(this, new PlaybackProgressEventArgs
|
||||
{
|
||||
MediaInfo = mediaInfo
|
||||
});
|
||||
}
|
||||
MediaInfo = mediaInfo
|
||||
});
|
||||
}
|
||||
|
||||
private void OnPlaybackStop(uBaseObject mediaInfo)
|
||||
{
|
||||
if (PlaybackStopped != null)
|
||||
|
||||
PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs
|
||||
{
|
||||
PlaybackStopped.Invoke(this, new PlaybackStoppedEventArgs
|
||||
{
|
||||
MediaInfo = mediaInfo
|
||||
});
|
||||
}
|
||||
MediaInfo = mediaInfo
|
||||
});
|
||||
}
|
||||
|
||||
private void OnMediaChanged(uBaseObject old, uBaseObject newMedia)
|
||||
{
|
||||
if (MediaChanged != null)
|
||||
MediaChanged?.Invoke(this, new MediaChangedEventArgs
|
||||
{
|
||||
MediaChanged.Invoke(this, new MediaChangedEventArgs
|
||||
{
|
||||
OldMediaInfo = old,
|
||||
NewMediaInfo = newMedia
|
||||
});
|
||||
}
|
||||
OldMediaInfo = old,
|
||||
NewMediaInfo = newMedia
|
||||
});
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
bool _disposed;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
DisposeTimer();
|
||||
}
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void DisposeTimer()
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_timer != null)
|
||||
if (_disposed)
|
||||
{
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_timer?.Dispose();
|
||||
}
|
||||
|
||||
_timer = null;
|
||||
Properties = null;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -42,30 +42,43 @@ namespace Emby.Dlna.PlayTo
|
||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||
private readonly string _serverAddress;
|
||||
private readonly string _accessToken;
|
||||
private readonly DateTime _creationTime;
|
||||
|
||||
public bool IsSessionActive => !_disposed && _device != null;
|
||||
|
||||
public bool SupportsMediaControl => IsSessionActive;
|
||||
|
||||
public PlayToController(SessionInfo session, ISessionManager sessionManager, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, string accessToken, IDeviceDiscovery deviceDiscovery, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IConfigurationManager config, IMediaEncoder mediaEncoder)
|
||||
public PlayToController(
|
||||
SessionInfo session,
|
||||
ISessionManager sessionManager,
|
||||
ILibraryManager libraryManager,
|
||||
ILogger logger,
|
||||
IDlnaManager dlnaManager,
|
||||
IUserManager userManager,
|
||||
IImageProcessor imageProcessor,
|
||||
string serverAddress,
|
||||
string accessToken,
|
||||
IDeviceDiscovery deviceDiscovery,
|
||||
IUserDataManager userDataManager,
|
||||
ILocalizationManager localization,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IConfigurationManager config,
|
||||
IMediaEncoder mediaEncoder)
|
||||
{
|
||||
_session = session;
|
||||
_sessionManager = sessionManager;
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
_dlnaManager = dlnaManager;
|
||||
_userManager = userManager;
|
||||
_imageProcessor = imageProcessor;
|
||||
_serverAddress = serverAddress;
|
||||
_accessToken = accessToken;
|
||||
_deviceDiscovery = deviceDiscovery;
|
||||
_userDataManager = userDataManager;
|
||||
_localization = localization;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_config = config;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_accessToken = accessToken;
|
||||
_logger = logger;
|
||||
_creationTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void Init(Device device)
|
||||
@@ -374,9 +387,7 @@ namespace Emby.Dlna.PlayTo
|
||||
return _device.IsPaused ? _device.SetPlay(CancellationToken.None) : _device.SetPause(CancellationToken.None);
|
||||
|
||||
case PlaystateCommand.Seek:
|
||||
{
|
||||
return Seek(command.SeekPositionTicks ?? 0);
|
||||
}
|
||||
return Seek(command.SeekPositionTicks ?? 0);
|
||||
|
||||
case PlaystateCommand.NextTrack:
|
||||
return SetPlaylistIndex(_currentPlaylistIndex + 1);
|
||||
@@ -442,8 +453,7 @@ namespace Emby.Dlna.PlayTo
|
||||
var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ??
|
||||
_dlnaManager.GetDefaultProfile();
|
||||
|
||||
var hasMediaSources = item as IHasMediaSources;
|
||||
var mediaSources = hasMediaSources != null
|
||||
var mediaSources = item is IHasMediaSources
|
||||
? (_mediaSourceManager.GetStaticMediaSources(item, true, user))
|
||||
: new List<MediaSourceInfo>();
|
||||
|
||||
@@ -452,7 +462,7 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken));
|
||||
|
||||
var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _libraryManager, _mediaEncoder)
|
||||
var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _mediaEncoder)
|
||||
.GetItemDidl(_config.GetDlnaConfiguration(), item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
|
||||
|
||||
playlistItem.Didl = itemXml;
|
||||
|
||||
@@ -16,7 +16,6 @@ using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Session;
|
||||
using MediaBrowser.Model.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
@@ -39,13 +38,12 @@ namespace Emby.Dlna.PlayTo
|
||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly ITimerFactory _timerFactory;
|
||||
|
||||
private bool _disposed;
|
||||
private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
|
||||
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, ITimerFactory timerFactory)
|
||||
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
|
||||
{
|
||||
_logger = logger;
|
||||
_sessionManager = sessionManager;
|
||||
@@ -61,7 +59,6 @@ namespace Emby.Dlna.PlayTo
|
||||
_localization = localization;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_timerFactory = timerFactory;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
@@ -92,11 +89,6 @@ namespace Emby.Dlna.PlayTo
|
||||
return;
|
||||
}
|
||||
|
||||
if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cancellationToken = _disposeCancellationTokenSource.Token;
|
||||
|
||||
await _sessionLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
@@ -108,6 +100,11 @@ namespace Emby.Dlna.PlayTo
|
||||
return;
|
||||
}
|
||||
|
||||
if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await AddDevice(info, location, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -162,17 +159,15 @@ namespace Emby.Dlna.PlayTo
|
||||
uuid = location.GetMD5().ToString("N");
|
||||
}
|
||||
|
||||
string deviceName = null;
|
||||
|
||||
var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion, uuid, deviceName, uri.OriginalString, null);
|
||||
var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion, uuid, null, uri.OriginalString, null);
|
||||
|
||||
var controller = sessionInfo.SessionControllers.OfType<PlayToController>().FirstOrDefault();
|
||||
|
||||
if (controller == null)
|
||||
{
|
||||
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger, _timerFactory, cancellationToken).ConfigureAwait(false);
|
||||
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
deviceName = device.Properties.Name;
|
||||
string deviceName = device.Properties.Name;
|
||||
|
||||
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
|
||||
|
||||
@@ -186,8 +181,6 @@ namespace Emby.Dlna.PlayTo
|
||||
serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress);
|
||||
}
|
||||
|
||||
string accessToken = null;
|
||||
|
||||
controller = new PlayToController(sessionInfo,
|
||||
_sessionManager,
|
||||
_libraryManager,
|
||||
@@ -196,7 +189,7 @@ namespace Emby.Dlna.PlayTo
|
||||
_userManager,
|
||||
_imageProcessor,
|
||||
serverAddress,
|
||||
accessToken,
|
||||
null,
|
||||
_deviceDiscovery,
|
||||
_userDataManager,
|
||||
_localization,
|
||||
|
||||
@@ -107,19 +107,19 @@ namespace Emby.Dlna.Server
|
||||
'&'
|
||||
};
|
||||
|
||||
private static readonly string[] s_escapeStringPairs = new string[]
|
||||
{
|
||||
"<",
|
||||
"<",
|
||||
">",
|
||||
">",
|
||||
"\"",
|
||||
""",
|
||||
"'",
|
||||
"'",
|
||||
"&",
|
||||
"&"
|
||||
};
|
||||
private static readonly string[] s_escapeStringPairs = new[]
|
||||
{
|
||||
"<",
|
||||
"<",
|
||||
">",
|
||||
">",
|
||||
"\"",
|
||||
""",
|
||||
"'",
|
||||
"'",
|
||||
"&",
|
||||
"&"
|
||||
};
|
||||
|
||||
private static string GetEscapeSequence(char c)
|
||||
{
|
||||
@@ -133,7 +133,7 @@ namespace Emby.Dlna.Server
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return c.ToString();
|
||||
return c.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>Replaces invalid XML characters in a string with their valid XML equivalent.</summary>
|
||||
@@ -145,6 +145,7 @@ namespace Emby.Dlna.Server
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder stringBuilder = null;
|
||||
int length = str.Length;
|
||||
int num = 0;
|
||||
@@ -230,9 +231,9 @@ namespace Emby.Dlna.Server
|
||||
|
||||
var serverName = new string(characters);
|
||||
|
||||
var name = (_profile.FriendlyName ?? string.Empty).Replace("${HostName}", serverName, StringComparison.OrdinalIgnoreCase);
|
||||
var name = _profile.FriendlyName?.Replace("${HostName}", serverName, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
return name;
|
||||
return name ?? string.Empty;
|
||||
}
|
||||
|
||||
private void AppendIconList(StringBuilder builder)
|
||||
@@ -295,65 +296,62 @@ namespace Emby.Dlna.Server
|
||||
}
|
||||
|
||||
private IEnumerable<DeviceIcon> GetIcons()
|
||||
{
|
||||
var list = new List<DeviceIcon>();
|
||||
|
||||
list.Add(new DeviceIcon
|
||||
=> new[]
|
||||
{
|
||||
MimeType = "image/png",
|
||||
Depth = "24",
|
||||
Width = 240,
|
||||
Height = 240,
|
||||
Url = "icons/logo240.png"
|
||||
});
|
||||
new DeviceIcon
|
||||
{
|
||||
MimeType = "image/png",
|
||||
Depth = "24",
|
||||
Width = 240,
|
||||
Height = 240,
|
||||
Url = "icons/logo240.png"
|
||||
},
|
||||
|
||||
list.Add(new DeviceIcon
|
||||
{
|
||||
MimeType = "image/jpeg",
|
||||
Depth = "24",
|
||||
Width = 240,
|
||||
Height = 240,
|
||||
Url = "icons/logo240.jpg"
|
||||
});
|
||||
new DeviceIcon
|
||||
{
|
||||
MimeType = "image/jpeg",
|
||||
Depth = "24",
|
||||
Width = 240,
|
||||
Height = 240,
|
||||
Url = "icons/logo240.jpg"
|
||||
},
|
||||
|
||||
list.Add(new DeviceIcon
|
||||
{
|
||||
MimeType = "image/png",
|
||||
Depth = "24",
|
||||
Width = 120,
|
||||
Height = 120,
|
||||
Url = "icons/logo120.png"
|
||||
});
|
||||
new DeviceIcon
|
||||
{
|
||||
MimeType = "image/png",
|
||||
Depth = "24",
|
||||
Width = 120,
|
||||
Height = 120,
|
||||
Url = "icons/logo120.png"
|
||||
},
|
||||
|
||||
list.Add(new DeviceIcon
|
||||
{
|
||||
MimeType = "image/jpeg",
|
||||
Depth = "24",
|
||||
Width = 120,
|
||||
Height = 120,
|
||||
Url = "icons/logo120.jpg"
|
||||
});
|
||||
new DeviceIcon
|
||||
{
|
||||
MimeType = "image/jpeg",
|
||||
Depth = "24",
|
||||
Width = 120,
|
||||
Height = 120,
|
||||
Url = "icons/logo120.jpg"
|
||||
},
|
||||
|
||||
list.Add(new DeviceIcon
|
||||
{
|
||||
MimeType = "image/png",
|
||||
Depth = "24",
|
||||
Width = 48,
|
||||
Height = 48,
|
||||
Url = "icons/logo48.png"
|
||||
});
|
||||
new DeviceIcon
|
||||
{
|
||||
MimeType = "image/png",
|
||||
Depth = "24",
|
||||
Width = 48,
|
||||
Height = 48,
|
||||
Url = "icons/logo48.png"
|
||||
},
|
||||
|
||||
list.Add(new DeviceIcon
|
||||
{
|
||||
MimeType = "image/jpeg",
|
||||
Depth = "24",
|
||||
Width = 48,
|
||||
Height = 48,
|
||||
Url = "icons/logo48.jpg"
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
new DeviceIcon
|
||||
{
|
||||
MimeType = "image/jpeg",
|
||||
Depth = "24",
|
||||
Width = 48,
|
||||
Height = 48,
|
||||
Url = "icons/logo48.jpg"
|
||||
}
|
||||
};
|
||||
|
||||
private IEnumerable<DeviceService> GetServices()
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@ using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Rssdp;
|
||||
using Rssdp.Infrastructure;
|
||||
@@ -48,20 +47,17 @@ namespace Emby.Dlna.Ssdp
|
||||
|
||||
private SsdpDeviceLocator _deviceLocator;
|
||||
|
||||
private readonly ITimerFactory _timerFactory;
|
||||
private readonly ISocketFactory _socketFactory;
|
||||
private ISsdpCommunicationsServer _commsServer;
|
||||
|
||||
public DeviceDiscovery(
|
||||
ILoggerFactory loggerFactory,
|
||||
IServerConfigurationManager config,
|
||||
ISocketFactory socketFactory,
|
||||
ITimerFactory timerFactory)
|
||||
ISocketFactory socketFactory)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger(nameof(DeviceDiscovery));
|
||||
_config = config;
|
||||
_socketFactory = socketFactory;
|
||||
_timerFactory = timerFactory;
|
||||
}
|
||||
|
||||
// Call this method from somewhere in your code to start the search.
|
||||
@@ -78,7 +74,7 @@ namespace Emby.Dlna.Ssdp
|
||||
{
|
||||
if (_listenerCount > 0 && _deviceLocator == null)
|
||||
{
|
||||
_deviceLocator = new SsdpDeviceLocator(_commsServer, _timerFactory);
|
||||
_deviceLocator = new SsdpDeviceLocator(_commsServer);
|
||||
|
||||
// (Optional) Set the filter so we only see notifications for devices we care about
|
||||
// (can be any search target value i.e device type, uuid value etc - any value that appears in the
|
||||
|
||||
@@ -5,12 +5,6 @@
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SkiaSharp" Version="1.68.0" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="1.68.0" />
|
||||
<PackageReference Include="Jellyfin.SkiaSharp.NativeAssets.LinuxArm" Version="1.68.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
||||
|
||||
@@ -18,7 +18,6 @@ using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Emby.Drawing
|
||||
{
|
||||
@@ -66,7 +65,7 @@ namespace Emby.Drawing
|
||||
_appPaths = appPaths;
|
||||
|
||||
ImageEnhancers = Array.Empty<IImageEnhancer>();
|
||||
|
||||
|
||||
ImageHelper.ImageProcessor = this;
|
||||
}
|
||||
|
||||
@@ -84,8 +83,8 @@ namespace Emby.Drawing
|
||||
}
|
||||
}
|
||||
|
||||
public string[] SupportedInputFormats =>
|
||||
new string[]
|
||||
public IReadOnlyCollection<string> SupportedInputFormats =>
|
||||
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"tiff",
|
||||
"tif",
|
||||
@@ -138,14 +137,14 @@ namespace Emby.Drawing
|
||||
}
|
||||
}
|
||||
|
||||
public ImageFormat[] GetSupportedImageOutputFormats()
|
||||
{
|
||||
return _imageEncoder.SupportedOutputFormats;
|
||||
}
|
||||
public IReadOnlyCollection<ImageFormat> GetSupportedImageOutputFormats()
|
||||
=> _imageEncoder.SupportedOutputFormats;
|
||||
|
||||
private static readonly HashSet<string> TransparentImageTypes
|
||||
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
|
||||
|
||||
private static readonly string[] TransparentImageTypes = new string[] { ".png", ".webp", ".gif" };
|
||||
public bool SupportsTransparency(string path)
|
||||
=> TransparentImageTypes.Contains(Path.GetExtension(path).ToLower());
|
||||
=> TransparentImageTypes.Contains(Path.GetExtension(path));
|
||||
|
||||
public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
|
||||
{
|
||||
@@ -168,10 +167,10 @@ namespace Emby.Drawing
|
||||
|
||||
string originalImagePath = originalImage.Path;
|
||||
DateTime dateModified = originalImage.DateModified;
|
||||
ImageSize? originalImageSize = null;
|
||||
ImageDimensions? originalImageSize = null;
|
||||
if (originalImage.Width > 0 && originalImage.Height > 0)
|
||||
{
|
||||
originalImageSize = new ImageSize(originalImage.Width, originalImage.Height);
|
||||
originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height);
|
||||
}
|
||||
|
||||
if (!_imageEncoder.SupportsImageEncoding)
|
||||
@@ -231,7 +230,7 @@ namespace Emby.Drawing
|
||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||
}
|
||||
|
||||
ImageSize newSize = ImageHelper.GetNewImageSize(options, null);
|
||||
ImageDimensions newSize = ImageHelper.GetNewImageSize(options, null);
|
||||
int quality = options.Quality;
|
||||
|
||||
ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency);
|
||||
@@ -245,7 +244,7 @@ namespace Emby.Drawing
|
||||
|
||||
try
|
||||
{
|
||||
if (!_fileSystem.FileExists(cacheFilePath))
|
||||
if (!File.Exists(cacheFilePath))
|
||||
{
|
||||
if (options.CropWhiteSpace && !SupportsTransparency(originalImagePath))
|
||||
{
|
||||
@@ -262,15 +261,6 @@ namespace Emby.Drawing
|
||||
|
||||
return (cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath));
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
// Decoder failed to decode it
|
||||
#if DEBUG
|
||||
_logger.LogError(ex, "Error encoding image");
|
||||
#endif
|
||||
// Just spit out the original file if all the options are default
|
||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// If it fails for whatever reason, return the original image
|
||||
@@ -334,7 +324,7 @@ namespace Emby.Drawing
|
||||
/// <summary>
|
||||
/// Gets the cache file path based on a set of parameters
|
||||
/// </summary>
|
||||
private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, int? blur, string backgroundColor, string foregroundLayer)
|
||||
private string GetCacheFilePath(string originalPath, ImageDimensions outputSize, int quality, DateTime dateModified, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, int? blur, string backgroundColor, string foregroundLayer)
|
||||
{
|
||||
var filename = originalPath
|
||||
+ "width=" + outputSize.Width
|
||||
@@ -375,29 +365,28 @@ namespace Emby.Drawing
|
||||
|
||||
filename += "v=" + Version;
|
||||
|
||||
return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLower());
|
||||
return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLowerInvariant());
|
||||
}
|
||||
|
||||
public ImageSize GetImageSize(BaseItem item, ItemImageInfo info)
|
||||
=> GetImageSize(item, info, true);
|
||||
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info)
|
||||
=> GetImageDimensions(item, info, true);
|
||||
|
||||
public ImageSize GetImageSize(BaseItem item, ItemImageInfo info, bool updateItem)
|
||||
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem)
|
||||
{
|
||||
int width = info.Width;
|
||||
int height = info.Height;
|
||||
|
||||
if (height > 0 && width > 0)
|
||||
{
|
||||
return new ImageSize(width, height);
|
||||
return new ImageDimensions(width, height);
|
||||
}
|
||||
|
||||
string path = info.Path;
|
||||
_logger.LogInformation("Getting image size for item {ItemType} {Path}", item.GetType().Name, path);
|
||||
|
||||
var size = GetImageSize(path);
|
||||
|
||||
info.Height = Convert.ToInt32(size.Height);
|
||||
info.Width = Convert.ToInt32(size.Width);
|
||||
ImageDimensions size = GetImageDimensions(path);
|
||||
info.Width = size.Width;
|
||||
info.Height = size.Height;
|
||||
|
||||
if (updateItem)
|
||||
{
|
||||
@@ -410,20 +399,8 @@ namespace Emby.Drawing
|
||||
/// <summary>
|
||||
/// Gets the size of the image.
|
||||
/// </summary>
|
||||
public ImageSize GetImageSize(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
using (var s = new SKFileStream(path))
|
||||
using (var codec = SKCodec.Create(s))
|
||||
{
|
||||
var info = codec.Info;
|
||||
return new ImageSize(info.Width, info.Height);
|
||||
}
|
||||
}
|
||||
public ImageDimensions GetImageDimensions(string path)
|
||||
=> _imageEncoder.GetImageSize(path);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the image cache tag.
|
||||
@@ -495,7 +472,7 @@ namespace Emby.Drawing
|
||||
return (originalImagePath, dateModified);
|
||||
}
|
||||
|
||||
if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat, StringComparer.OrdinalIgnoreCase))
|
||||
if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat))
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -640,12 +617,12 @@ namespace Emby.Drawing
|
||||
try
|
||||
{
|
||||
// Check again in case of contention
|
||||
if (_fileSystem.FileExists(enhancedImagePath))
|
||||
if (File.Exists(enhancedImagePath))
|
||||
{
|
||||
return (enhancedImagePath, treatmentRequiresTransparency);
|
||||
}
|
||||
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(enhancedImagePath));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(enhancedImagePath));
|
||||
|
||||
await ExecuteImageEnhancers(supportedEnhancers, originalImagePath, enhancedImagePath, item, imageType, imageIndex).ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
|
||||
@@ -6,15 +7,11 @@ namespace Emby.Drawing
|
||||
{
|
||||
public class NullImageEncoder : IImageEncoder
|
||||
{
|
||||
public string[] SupportedInputFormats =>
|
||||
new[]
|
||||
{
|
||||
"png",
|
||||
"jpeg",
|
||||
"jpg"
|
||||
};
|
||||
public IReadOnlyCollection<string> SupportedInputFormats
|
||||
=> new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "png", "jpeg", "jpg" };
|
||||
|
||||
public ImageFormat[] SupportedOutputFormats => new[] { ImageFormat.Jpg, ImageFormat.Png };
|
||||
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
|
||||
=> new HashSet<ImageFormat>() { ImageFormat.Jpg, ImageFormat.Png };
|
||||
|
||||
public void CropWhiteSpace(string inputPath, string outputPath)
|
||||
{
|
||||
@@ -37,7 +34,7 @@ namespace Emby.Drawing
|
||||
|
||||
public bool SupportsImageEncoding => false;
|
||||
|
||||
public ImageSize GetImageSize(string path)
|
||||
public ImageDimensions GetImageSize(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace IsoMounter
|
||||
_logger = logger;
|
||||
ProcessFactory = processFactory;
|
||||
|
||||
MountPointRoot = FileSystem.DirectorySeparatorChar + "tmp" + FileSystem.DirectorySeparatorChar + "Emby";
|
||||
MountPointRoot = Path.DirectorySeparatorChar + "tmp" + Path.DirectorySeparatorChar + "Emby";
|
||||
|
||||
_logger.LogDebug(
|
||||
"[{0}] System PATH is currently set to [{1}].",
|
||||
@@ -121,7 +121,7 @@ namespace IsoMounter
|
||||
path,
|
||||
Path.GetExtension(path),
|
||||
EnvironmentInfo.OperatingSystem,
|
||||
ExecutablesAvailable.ToString()
|
||||
ExecutablesAvailable
|
||||
);
|
||||
|
||||
if (ExecutablesAvailable)
|
||||
@@ -183,7 +183,7 @@ namespace IsoMounter
|
||||
_logger.LogInformation(
|
||||
"[{0}] Disposing [{1}].",
|
||||
Name,
|
||||
disposing.ToString()
|
||||
disposing
|
||||
);
|
||||
|
||||
if (disposing)
|
||||
@@ -214,9 +214,9 @@ namespace IsoMounter
|
||||
{
|
||||
string path = test.Trim();
|
||||
|
||||
if (!string.IsNullOrEmpty(path) && FileSystem.FileExists(path = Path.Combine(path, name)))
|
||||
if (!string.IsNullOrEmpty(path) && File.Exists(path = Path.Combine(path, name)))
|
||||
{
|
||||
return FileSystem.GetFullPath(path);
|
||||
return Path.GetFullPath(path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,9 +229,8 @@ namespace IsoMounter
|
||||
var uid = getuid();
|
||||
|
||||
_logger.LogDebug(
|
||||
"[{0}] Our current UID is [{1}], GetUserId() returned [{2}].",
|
||||
"[{0}] GetUserId() returned [{2}].",
|
||||
Name,
|
||||
uid.ToString(),
|
||||
uid
|
||||
);
|
||||
|
||||
@@ -327,7 +326,7 @@ namespace IsoMounter
|
||||
|
||||
try
|
||||
{
|
||||
FileSystem.CreateDirectory(mountPoint);
|
||||
Directory.CreateDirectory(mountPoint);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
@@ -377,7 +376,7 @@ namespace IsoMounter
|
||||
|
||||
try
|
||||
{
|
||||
FileSystem.DeleteDirectory(mountPoint, false);
|
||||
Directory.Delete(mountPoint, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -455,7 +454,7 @@ namespace IsoMounter
|
||||
|
||||
try
|
||||
{
|
||||
FileSystem.DeleteDirectory(mount.MountedPath, false);
|
||||
Directory.Delete(mount.MountedPath, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Emby.Naming.AudioBook
|
||||
return null;
|
||||
}
|
||||
|
||||
var extension = Path.GetExtension(path) ?? string.Empty;
|
||||
var extension = Path.GetExtension(path);
|
||||
// Check supported extensions
|
||||
if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
|
||||
@@ -175,71 +175,23 @@ namespace Emby.Naming.Video
|
||||
return videos;
|
||||
}
|
||||
|
||||
var list = new List<VideoInfo>();
|
||||
|
||||
var folderName = Path.GetFileName(Path.GetDirectoryName(videos[0].Files[0].Path));
|
||||
|
||||
if (!string.IsNullOrEmpty(folderName) && folderName.Length > 1)
|
||||
{
|
||||
if (videos.All(i => i.Files.Count == 1 && IsEligibleForMultiVersion(folderName, i.Files[0].Path)))
|
||||
var ordered = videos.OrderBy(i => i.Name);
|
||||
|
||||
return ordered.GroupBy(v => new {v.Name, v.Year}).Select(group => new VideoInfo
|
||||
{
|
||||
// Enforce the multi-version limit
|
||||
if (videos.Count <= 8 && HaveSameYear(videos))
|
||||
{
|
||||
var ordered = videos.OrderBy(i => i.Name).ToList();
|
||||
|
||||
list.Add(ordered[0]);
|
||||
|
||||
list[0].AlternateVersions = ordered.Skip(1).Select(i => i.Files[0]).ToList();
|
||||
list[0].Name = folderName;
|
||||
list[0].Extras.AddRange(ordered.Skip(1).SelectMany(i => i.Extras));
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
Name = folderName,
|
||||
Year = group.First().Year,
|
||||
Files = group.First().Files,
|
||||
AlternateVersions = group.Skip(1).Select(i => i.Files[0]).ToList(),
|
||||
Extras = group.First().Extras.Concat(group.Skip(1).SelectMany(i => i.Extras)).ToList()
|
||||
});
|
||||
}
|
||||
|
||||
return videos;
|
||||
//foreach (var video in videos.OrderBy(i => i.Name))
|
||||
//{
|
||||
// var match = list
|
||||
// .FirstOrDefault(i => string.Equals(i.Name, video.Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// if (match != null && video.Files.Count == 1 && match.Files.Count == 1)
|
||||
// {
|
||||
// match.AlternateVersions.Add(video.Files[0]);
|
||||
// match.Extras.AddRange(video.Extras);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// list.Add(video);
|
||||
// }
|
||||
//}
|
||||
|
||||
//return list;
|
||||
}
|
||||
|
||||
private bool HaveSameYear(List<VideoInfo> videos)
|
||||
{
|
||||
return videos.Select(i => i.Year ?? -1).Distinct().Count() < 2;
|
||||
}
|
||||
|
||||
private bool IsEligibleForMultiVersion(string folderName, string testFilename)
|
||||
{
|
||||
testFilename = Path.GetFileNameWithoutExtension(testFilename);
|
||||
|
||||
if (string.Equals(folderName, testFilename, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
testFilename = testFilename.Substring(folderName.Length).Trim();
|
||||
return testFilename.StartsWith("-", StringComparison.OrdinalIgnoreCase) || Regex.Replace(testFilename, @"\[([^]]*)\]", "").Trim() == string.Empty;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<VideoFileInfo> GetExtras(IEnumerable<VideoFileInfo> remainingFiles, List<string> baseNames)
|
||||
|
||||
@@ -73,11 +73,6 @@ namespace Emby.Notifications
|
||||
Type = NotificationType.AudioPlayback.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.GamePlayback.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.VideoPlayback.ToString()
|
||||
@@ -88,11 +83,6 @@ namespace Emby.Notifications
|
||||
Type = NotificationType.AudioPlaybackStopped.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.GamePlaybackStopped.ToString()
|
||||
},
|
||||
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.VideoPlaybackStopped.ToString()
|
||||
|
||||
@@ -20,7 +20,6 @@ using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Notifications;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using MediaBrowser.Model.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Notifications
|
||||
@@ -40,9 +39,8 @@ namespace Emby.Notifications
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly ITimerFactory _timerFactory;
|
||||
|
||||
private ITimer LibraryUpdateTimer { get; set; }
|
||||
private Timer LibraryUpdateTimer { get; set; }
|
||||
private readonly object _libraryChangedSyncLock = new object();
|
||||
|
||||
private readonly IConfigurationManager _config;
|
||||
@@ -52,7 +50,7 @@ namespace Emby.Notifications
|
||||
|
||||
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, ITimerFactory timerFactory)
|
||||
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)
|
||||
{
|
||||
_installationManager = installationManager;
|
||||
_userManager = userManager;
|
||||
@@ -64,19 +62,20 @@ namespace Emby.Notifications
|
||||
_appHost = appHost;
|
||||
_config = config;
|
||||
_deviceManager = deviceManager;
|
||||
_timerFactory = timerFactory;
|
||||
_localization = localization;
|
||||
_activityManager = activityManager;
|
||||
|
||||
_coreNotificationTypes = new CoreNotificationTypes(localization, appHost).GetNotificationTypes().Select(i => i.Type).ToArray();
|
||||
}
|
||||
|
||||
public void Run()
|
||||
public Task RunAsync()
|
||||
{
|
||||
_libraryManager.ItemAdded += _libraryManager_ItemAdded;
|
||||
_appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged;
|
||||
_appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged;
|
||||
_activityManager.EntryCreated += _activityManager_EntryCreated;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async void _appHost_HasPendingRestartChanged(object sender, EventArgs e)
|
||||
@@ -157,7 +156,7 @@ namespace Emby.Notifications
|
||||
{
|
||||
if (LibraryUpdateTimer == null)
|
||||
{
|
||||
LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, 5000,
|
||||
LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, 5000,
|
||||
Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -181,12 +181,12 @@ namespace Emby.Photos
|
||||
|
||||
try
|
||||
{
|
||||
var size = _imageProcessor.GetImageSize(item, img, false);
|
||||
var size = _imageProcessor.GetImageDimensions(item, img, false);
|
||||
|
||||
if (size.Width > 0 && size.Height > 0)
|
||||
{
|
||||
item.Width = Convert.ToInt32(size.Width);
|
||||
item.Height = Convert.ToInt32(size.Height);
|
||||
item.Width = size.Width;
|
||||
item.Height = size.Height;
|
||||
}
|
||||
}
|
||||
catch (ArgumentException)
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
_deviceManager = deviceManager;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
public Task RunAsync()
|
||||
{
|
||||
_taskManager.TaskCompleted += _taskManager_TaskCompleted;
|
||||
|
||||
@@ -91,20 +91,22 @@ namespace Emby.Server.Implementations.Activity
|
||||
_deviceManager.CameraImageUploaded += _deviceManager_CameraImageUploaded;
|
||||
|
||||
_appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
async void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
|
||||
void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("CameraImageUploadedFrom"), e.Argument.Device.Name),
|
||||
Type = NotificationType.CameraImageUploaded.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
async void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e)
|
||||
void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("UserLockedOutWithName"), e.Argument.Name),
|
||||
Type = NotificationType.UserLockedOut.ToString(),
|
||||
@@ -112,9 +114,9 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
async void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
||||
void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), e.Provider, Notifications.Notifications.GetItemName(e.Item)),
|
||||
Type = "SubtitleDownloadFailure",
|
||||
@@ -123,7 +125,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
async void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e)
|
||||
void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e)
|
||||
{
|
||||
var item = e.MediaInfo;
|
||||
|
||||
@@ -146,7 +148,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
|
||||
var user = e.Users.First();
|
||||
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
|
||||
Type = GetPlaybackStoppedNotificationType(item.MediaType),
|
||||
@@ -154,7 +156,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
async void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
|
||||
void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
|
||||
{
|
||||
var item = e.MediaInfo;
|
||||
|
||||
@@ -177,7 +179,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
|
||||
var user = e.Users.First();
|
||||
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("UserStartedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
|
||||
Type = GetPlaybackNotificationType(item.MediaType),
|
||||
@@ -208,10 +210,6 @@ namespace Emby.Server.Implementations.Activity
|
||||
{
|
||||
return NotificationType.AudioPlayback.ToString();
|
||||
}
|
||||
if (string.Equals(mediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return NotificationType.GamePlayback.ToString();
|
||||
}
|
||||
if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return NotificationType.VideoPlayback.ToString();
|
||||
@@ -226,10 +224,6 @@ namespace Emby.Server.Implementations.Activity
|
||||
{
|
||||
return NotificationType.AudioPlaybackStopped.ToString();
|
||||
}
|
||||
if (string.Equals(mediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return NotificationType.GamePlaybackStopped.ToString();
|
||||
}
|
||||
if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return NotificationType.VideoPlaybackStopped.ToString();
|
||||
@@ -238,7 +232,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
return null;
|
||||
}
|
||||
|
||||
async void _sessionManager_SessionEnded(object sender, SessionEventArgs e)
|
||||
void _sessionManager_SessionEnded(object sender, SessionEventArgs e)
|
||||
{
|
||||
string name;
|
||||
var session = e.SessionInfo;
|
||||
@@ -255,7 +249,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
name = string.Format(_localization.GetLocalizedString("UserOfflineFromDevice"), session.UserName, session.DeviceName);
|
||||
}
|
||||
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = name,
|
||||
Type = "SessionEnded",
|
||||
@@ -264,11 +258,11 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
async void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
|
||||
void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
|
||||
{
|
||||
var user = e.Argument.User;
|
||||
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("AuthenticationSucceededWithUserName"), user.Name),
|
||||
Type = "AuthenticationSucceeded",
|
||||
@@ -277,9 +271,9 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
async void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
|
||||
void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("FailedLoginAttemptWithUserName"), e.Argument.Username),
|
||||
Type = "AuthenticationFailed",
|
||||
@@ -288,9 +282,9 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
async void _appHost_ApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e)
|
||||
void _appHost_ApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("MessageApplicationUpdatedTo"), e.Argument.versionStr),
|
||||
Type = NotificationType.ApplicationUpdateInstalled.ToString(),
|
||||
@@ -298,27 +292,27 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
async void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
|
||||
void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("MessageNamedServerConfigurationUpdatedWithValue"), e.Key),
|
||||
Type = "NamedConfigurationUpdated"
|
||||
});
|
||||
}
|
||||
|
||||
async void _config_ConfigurationUpdated(object sender, EventArgs e)
|
||||
void _config_ConfigurationUpdated(object sender, EventArgs e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = _localization.GetLocalizedString("MessageServerConfigurationUpdated"),
|
||||
Type = "ServerConfigurationUpdated"
|
||||
});
|
||||
}
|
||||
|
||||
async void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
||||
void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("UserPolicyUpdatedWithName"), e.Argument.Name),
|
||||
Type = "UserPolicyUpdated",
|
||||
@@ -326,18 +320,18 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
async void _userManager_UserDeleted(object sender, GenericEventArgs<User> e)
|
||||
void _userManager_UserDeleted(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("UserDeletedWithName"), e.Argument.Name),
|
||||
Type = "UserDeleted"
|
||||
});
|
||||
}
|
||||
|
||||
async void _userManager_UserPasswordChanged(object sender, GenericEventArgs<User> e)
|
||||
void _userManager_UserPasswordChanged(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("UserPasswordChangedWithName"), e.Argument.Name),
|
||||
Type = "UserPasswordChanged",
|
||||
@@ -345,9 +339,9 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
async void _userManager_UserCreated(object sender, GenericEventArgs<User> e)
|
||||
void _userManager_UserCreated(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("UserCreatedWithName"), e.Argument.Name),
|
||||
Type = "UserCreated",
|
||||
@@ -355,9 +349,9 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
async void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e)
|
||||
void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("SubtitlesDownloadedForItem"), Notifications.Notifications.GetItemName(e.Item)),
|
||||
Type = "SubtitlesDownloaded",
|
||||
@@ -366,7 +360,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
async void _sessionManager_SessionStarted(object sender, SessionEventArgs e)
|
||||
void _sessionManager_SessionStarted(object sender, SessionEventArgs e)
|
||||
{
|
||||
string name;
|
||||
var session = e.SessionInfo;
|
||||
@@ -383,7 +377,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
name = string.Format(_localization.GetLocalizedString("UserOnlineFromDevice"), session.UserName, session.DeviceName);
|
||||
}
|
||||
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = name,
|
||||
Type = "SessionStarted",
|
||||
@@ -392,9 +386,9 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
async void _installationManager_PluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e)
|
||||
void _installationManager_PluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("PluginUpdatedWithName"), e.Argument.Item1.Name),
|
||||
Type = NotificationType.PluginUpdateInstalled.ToString(),
|
||||
@@ -403,18 +397,18 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
async void _installationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
|
||||
void _installationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("PluginUninstalledWithName"), e.Argument.Name),
|
||||
Type = NotificationType.PluginUninstalled.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
async void _installationManager_PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
|
||||
void _installationManager_PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("PluginInstalledWithName"), e.Argument.name),
|
||||
Type = NotificationType.PluginInstalled.ToString(),
|
||||
@@ -422,11 +416,11 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
async void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
||||
void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
||||
{
|
||||
var installationInfo = e.InstallationInfo;
|
||||
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("NameInstallFailed"), installationInfo.Name),
|
||||
Type = NotificationType.InstallationFailed.ToString(),
|
||||
@@ -435,7 +429,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
async void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e)
|
||||
void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e)
|
||||
{
|
||||
var result = e.Result;
|
||||
var task = e.Task;
|
||||
@@ -462,7 +456,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
vals.Add(e.Result.LongErrorMessage);
|
||||
}
|
||||
|
||||
await CreateLogEntry(new ActivityLogEntry
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
|
||||
Type = NotificationType.TaskFailed.ToString(),
|
||||
@@ -473,11 +467,11 @@ namespace Emby.Server.Implementations.Activity
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateLogEntry(ActivityLogEntry entry)
|
||||
private void CreateLogEntry(ActivityLogEntry entry)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _activityManager.CreateAsync(entry);
|
||||
_activityManager.Create(entry);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -502,7 +496,6 @@ namespace Emby.Server.Implementations.Activity
|
||||
_sessionManager.PlaybackStart -= _sessionManager_PlaybackStart;
|
||||
_sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped;
|
||||
|
||||
_subManager.SubtitlesDownloaded -= _subManager_SubtitlesDownloaded;
|
||||
_subManager.SubtitleDownloadFailure -= _subManager_SubtitleDownloadFailure;
|
||||
|
||||
_userManager.UserCreated -= _userManager_UserCreated;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Activity
|
||||
@@ -27,38 +26,20 @@ namespace Emby.Server.Implementations.Activity
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
public async Task CreateAsync(ActivityLogEntry entry)
|
||||
public void Create(ActivityLogEntry entry)
|
||||
{
|
||||
entry.Date = DateTime.UtcNow;
|
||||
|
||||
await _repo.CreateAsync(entry);
|
||||
_repo.Create(entry);
|
||||
|
||||
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(entry));
|
||||
}
|
||||
|
||||
public IEnumerable<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
|
||||
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
|
||||
{
|
||||
var result = _repo.GetActivityLogEntries();
|
||||
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
|
||||
|
||||
if (minDate.HasValue)
|
||||
{
|
||||
result = result.Where(x => x.Date >= minDate.Value);
|
||||
}
|
||||
if (hasUserId.HasValue)
|
||||
{
|
||||
result = result.Where(x => x.UserId != null && x.UserId != Guid.Empty);
|
||||
}
|
||||
if (startIndex.HasValue)
|
||||
{
|
||||
result = result.Where(x => x.Id >= startIndex.Value);
|
||||
}
|
||||
if (limit.HasValue)
|
||||
{
|
||||
result = result.Take(limit.Value);
|
||||
}
|
||||
|
||||
// Add images for each user
|
||||
foreach (var item in result)
|
||||
foreach (var item in result.Items.Where(i => !i.UserId.Equals(Guid.Empty)))
|
||||
{
|
||||
var user = _userManager.GetUserById(item.UserId);
|
||||
|
||||
@@ -69,7 +50,12 @@ namespace Emby.Server.Implementations.Activity
|
||||
}
|
||||
}
|
||||
|
||||
return result.AsEnumerable();
|
||||
return result;
|
||||
}
|
||||
|
||||
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
|
||||
{
|
||||
return GetActivityLogEntries(minDate, null, startIndex, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,310 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Data;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
namespace Emby.Server.Implementations.Activity
|
||||
{
|
||||
public class ActivityRepository : DbContext, IActivityRepository
|
||||
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
|
||||
{
|
||||
protected string _dataDirPath;
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
protected IFileSystem FileSystem { get; private set; }
|
||||
|
||||
public DbSet<ActivityLogEntry> ActivityLogs { get; set; }
|
||||
|
||||
public ActivityRepository(string dataDirPath)
|
||||
public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem)
|
||||
: base(loggerFactory.CreateLogger(nameof(ActivityRepository)))
|
||||
{
|
||||
_dataDirPath = dataDirPath;
|
||||
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
|
||||
FileSystem = fileSystem;
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
public void Initialize()
|
||||
{
|
||||
// Ensure the dir exists
|
||||
Directory.CreateDirectory(_dataDirPath);
|
||||
try
|
||||
{
|
||||
InitializeInternal();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error loading database file. Will reset and retry.");
|
||||
|
||||
optionsBuilder.UseSqlite($"Filename={Path.Combine(_dataDirPath, "activitylog.sqlite.db")}");
|
||||
FileSystem.DeleteFile(DbFilePath);
|
||||
|
||||
InitializeInternal();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateAsync(ActivityLogEntry entry)
|
||||
private void InitializeInternal()
|
||||
{
|
||||
await ActivityLogs.AddAsync(entry);
|
||||
await SaveChangesAsync();
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
RunDefaultInitialization(connection);
|
||||
|
||||
connection.RunQueries(new[]
|
||||
{
|
||||
"create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)",
|
||||
"drop index if exists idx_ActivityLogEntries"
|
||||
});
|
||||
|
||||
TryMigrate(connection);
|
||||
}
|
||||
}
|
||||
|
||||
public IQueryable<ActivityLogEntry> GetActivityLogEntries()
|
||||
=> ActivityLogs;
|
||||
private void TryMigrate(ManagedConnection connection)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (TableExists(connection, "ActivityLogEntries"))
|
||||
{
|
||||
connection.RunQueries(new[]
|
||||
{
|
||||
"INSERT INTO ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) SELECT Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity FROM ActivityLogEntries",
|
||||
"drop table if exists ActivityLogEntries"
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error migrating activity log database");
|
||||
}
|
||||
}
|
||||
|
||||
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
|
||||
|
||||
public void Create(ActivityLogEntry entry)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entry));
|
||||
}
|
||||
|
||||
using (WriteLock.Write())
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"))
|
||||
{
|
||||
statement.TryBind("@Name", entry.Name);
|
||||
|
||||
statement.TryBind("@Overview", entry.Overview);
|
||||
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
||||
statement.TryBind("@Type", entry.Type);
|
||||
statement.TryBind("@ItemId", entry.ItemId);
|
||||
|
||||
if (entry.UserId.Equals(Guid.Empty))
|
||||
{
|
||||
statement.TryBindNull("@UserId");
|
||||
}
|
||||
else
|
||||
{
|
||||
statement.TryBind("@UserId", entry.UserId.ToString("N"));
|
||||
}
|
||||
|
||||
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
||||
statement.TryBind("@LogSeverity", entry.Severity.ToString());
|
||||
|
||||
statement.MoveNext();
|
||||
}
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(ActivityLogEntry entry)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entry));
|
||||
}
|
||||
|
||||
using (WriteLock.Write())
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id"))
|
||||
{
|
||||
statement.TryBind("@Id", entry.Id);
|
||||
|
||||
statement.TryBind("@Name", entry.Name);
|
||||
statement.TryBind("@Overview", entry.Overview);
|
||||
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
||||
statement.TryBind("@Type", entry.Type);
|
||||
statement.TryBind("@ItemId", entry.ItemId);
|
||||
|
||||
if (entry.UserId.Equals(Guid.Empty))
|
||||
{
|
||||
statement.TryBindNull("@UserId");
|
||||
}
|
||||
else
|
||||
{
|
||||
statement.TryBind("@UserId", entry.UserId.ToString("N"));
|
||||
}
|
||||
|
||||
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
||||
statement.TryBind("@LogSeverity", entry.Severity.ToString());
|
||||
|
||||
statement.MoveNext();
|
||||
}
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
|
||||
{
|
||||
using (WriteLock.Read())
|
||||
using (var connection = CreateConnection(true))
|
||||
{
|
||||
var commandText = BaseActivitySelectText;
|
||||
var whereClauses = new List<string>();
|
||||
|
||||
if (minDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("DateCreated>=@DateCreated");
|
||||
}
|
||||
if (hasUserId.HasValue)
|
||||
{
|
||||
if (hasUserId.Value)
|
||||
{
|
||||
whereClauses.Add("UserId not null");
|
||||
}
|
||||
else
|
||||
{
|
||||
whereClauses.Add("UserId is null");
|
||||
}
|
||||
}
|
||||
|
||||
var whereTextWithoutPaging = whereClauses.Count == 0 ?
|
||||
string.Empty :
|
||||
" where " + string.Join(" AND ", whereClauses.ToArray());
|
||||
|
||||
if (startIndex.HasValue && startIndex.Value > 0)
|
||||
{
|
||||
var pagingWhereText = whereClauses.Count == 0 ?
|
||||
string.Empty :
|
||||
" where " + string.Join(" AND ", whereClauses.ToArray());
|
||||
|
||||
whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})",
|
||||
pagingWhereText,
|
||||
startIndex.Value.ToString(_usCulture)));
|
||||
}
|
||||
|
||||
var whereText = whereClauses.Count == 0 ?
|
||||
string.Empty :
|
||||
" where " + string.Join(" AND ", whereClauses.ToArray());
|
||||
|
||||
commandText += whereText;
|
||||
|
||||
commandText += " ORDER BY DateCreated DESC";
|
||||
|
||||
if (limit.HasValue)
|
||||
{
|
||||
commandText += " LIMIT " + limit.Value.ToString(_usCulture);
|
||||
}
|
||||
|
||||
var statementTexts = new List<string>();
|
||||
statementTexts.Add(commandText);
|
||||
statementTexts.Add("select count (Id) from ActivityLog" + whereTextWithoutPaging);
|
||||
|
||||
return connection.RunInTransaction(db =>
|
||||
{
|
||||
var list = new List<ActivityLogEntry>();
|
||||
var result = new QueryResult<ActivityLogEntry>();
|
||||
|
||||
var statements = PrepareAllSafe(db, statementTexts).ToList();
|
||||
|
||||
using (var statement = statements[0])
|
||||
{
|
||||
if (minDate.HasValue)
|
||||
{
|
||||
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
|
||||
}
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
list.Add(GetEntry(row));
|
||||
}
|
||||
}
|
||||
|
||||
using (var statement = statements[1])
|
||||
{
|
||||
if (minDate.HasValue)
|
||||
{
|
||||
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
|
||||
}
|
||||
|
||||
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
|
||||
}
|
||||
|
||||
result.Items = list.ToArray();
|
||||
return result;
|
||||
|
||||
}, ReadTransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
private static ActivityLogEntry GetEntry(IReadOnlyList<IResultSetValue> reader)
|
||||
{
|
||||
var index = 0;
|
||||
|
||||
var info = new ActivityLogEntry
|
||||
{
|
||||
Id = reader[index].ToInt64()
|
||||
};
|
||||
|
||||
index++;
|
||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.Name = reader[index].ToString();
|
||||
}
|
||||
|
||||
index++;
|
||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.Overview = reader[index].ToString();
|
||||
}
|
||||
|
||||
index++;
|
||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.ShortOverview = reader[index].ToString();
|
||||
}
|
||||
|
||||
index++;
|
||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.Type = reader[index].ToString();
|
||||
}
|
||||
|
||||
index++;
|
||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.ItemId = reader[index].ToString();
|
||||
}
|
||||
|
||||
index++;
|
||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.UserId = new Guid(reader[index].ToString());
|
||||
}
|
||||
|
||||
index++;
|
||||
info.Date = reader[index].ReadDateTime();
|
||||
|
||||
index++;
|
||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.Severity = (LogLevel)Enum.Parse(typeof(LogLevel), reader[index].ToString(), true);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@ namespace Emby.Server.Implementations.AppBase
|
||||
string programDataPath,
|
||||
string appFolderPath,
|
||||
string logDirectoryPath = null,
|
||||
string configurationDirectoryPath = null)
|
||||
string configurationDirectoryPath = null,
|
||||
string cacheDirectoryPath = null)
|
||||
{
|
||||
ProgramDataPath = programDataPath;
|
||||
ProgramSystemPath = appFolderPath;
|
||||
LogDirectoryPath = logDirectoryPath;
|
||||
ConfigurationDirectoryPath = configurationDirectoryPath;
|
||||
CachePath = cacheDirectoryPath;
|
||||
}
|
||||
|
||||
public string ProgramDataPath { get; private set; }
|
||||
|
||||
@@ -127,7 +127,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
Logger.LogInformation("Saving system configuration");
|
||||
var path = CommonApplicationPaths.SystemConfigurationFilePath;
|
||||
|
||||
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
lock (_configurationSyncLock)
|
||||
{
|
||||
@@ -171,16 +171,29 @@ namespace Emby.Server.Implementations.AppBase
|
||||
private void UpdateCachePath()
|
||||
{
|
||||
string cachePath;
|
||||
|
||||
// If the configuration file has no entry (i.e. not set in UI)
|
||||
if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath))
|
||||
{
|
||||
cachePath = null;
|
||||
// If the current live configuration has no entry (i.e. not set on CLI/envvars, during startup)
|
||||
if (string.IsNullOrWhiteSpace(((BaseApplicationPaths)CommonApplicationPaths).CachePath))
|
||||
{
|
||||
// Set cachePath to a default value under ProgramDataPath
|
||||
cachePath = Path.Combine(((BaseApplicationPaths)CommonApplicationPaths).ProgramDataPath, "cache");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set cachePath to the existing live value; will require restart if UI value is removed (but not replaced)
|
||||
// TODO: Figure out how to re-grab this from the CLI/envvars while running
|
||||
cachePath = ((BaseApplicationPaths)CommonApplicationPaths).CachePath;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cachePath = Path.Combine(CommonConfiguration.CachePath, "cache");
|
||||
// Set cachePath to the new UI-set value
|
||||
cachePath = CommonConfiguration.CachePath;
|
||||
}
|
||||
|
||||
Logger.LogInformation("Setting cache path to " + cachePath);
|
||||
((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath;
|
||||
}
|
||||
|
||||
@@ -197,7 +210,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
&& !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath))
|
||||
{
|
||||
// Validate
|
||||
if (!FileSystem.DirectoryExists(newPath))
|
||||
if (!Directory.Exists(newPath))
|
||||
{
|
||||
throw new FileNotFoundException(string.Format("{0} does not exist.", newPath));
|
||||
}
|
||||
@@ -209,8 +222,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
protected void EnsureWriteAccess(string path)
|
||||
{
|
||||
var file = Path.Combine(path, Guid.NewGuid().ToString());
|
||||
|
||||
FileSystem.WriteAllText(file, string.Empty);
|
||||
File.WriteAllText(file, string.Empty);
|
||||
FileSystem.DeleteFile(file);
|
||||
}
|
||||
|
||||
@@ -218,7 +230,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
|
||||
private string GetConfigurationFile(string key)
|
||||
{
|
||||
return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLower() + ".xml");
|
||||
return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLowerInvariant() + ".xml");
|
||||
}
|
||||
|
||||
public object GetConfiguration(string key)
|
||||
@@ -246,14 +258,15 @@ namespace Emby.Server.Implementations.AppBase
|
||||
|
||||
private object LoadConfiguration(string path, Type configurationType)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return Activator.CreateInstance(configurationType);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return XmlSerializer.DeserializeFromFile(configurationType, path);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return Activator.CreateInstance(configurationType);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return Activator.CreateInstance(configurationType);
|
||||
@@ -293,7 +306,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
|
||||
|
||||
var path = GetConfigurationFile(key);
|
||||
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
lock (_configurationSyncLock)
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
// Use try/catch to avoid the extra file system lookup using File.Exists
|
||||
try
|
||||
{
|
||||
buffer = fileSystem.ReadAllBytes(path);
|
||||
buffer = File.ReadAllBytes(path);
|
||||
|
||||
configuration = xmlSerializer.DeserializeFromBytes(type, buffer);
|
||||
}
|
||||
@@ -48,10 +48,10 @@ namespace Emby.Server.Implementations.AppBase
|
||||
// If the file didn't exist before, or if something has changed, re-save
|
||||
if (buffer == null || !buffer.SequenceEqual(newBytes))
|
||||
{
|
||||
fileSystem.CreateDirectory(fileSystem.GetDirectoryName(path));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
// Save it after load in case we got new items
|
||||
fileSystem.WriteAllBytes(path, newBytes);
|
||||
File.WriteAllBytes(path, newBytes);
|
||||
}
|
||||
|
||||
return configuration;
|
||||
|
||||
@@ -12,7 +12,6 @@ using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Common.Implementations.Serialization;
|
||||
using Emby.Dlna;
|
||||
using Emby.Dlna.Main;
|
||||
using Emby.Dlna.Ssdp;
|
||||
@@ -43,7 +42,6 @@ using Emby.Server.Implementations.ScheduledTasks;
|
||||
using Emby.Server.Implementations.Security;
|
||||
using Emby.Server.Implementations.Serialization;
|
||||
using Emby.Server.Implementations.Session;
|
||||
using Emby.Server.Implementations.Threading;
|
||||
using Emby.Server.Implementations.TV;
|
||||
using Emby.Server.Implementations.Updates;
|
||||
using Emby.Server.Implementations.Xml;
|
||||
@@ -99,7 +97,6 @@ using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Services;
|
||||
using MediaBrowser.Model.System;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using MediaBrowser.Model.Threading;
|
||||
using MediaBrowser.Model.Updates;
|
||||
using MediaBrowser.Model.Xml;
|
||||
using MediaBrowser.Providers.Chapters;
|
||||
@@ -110,9 +107,7 @@ using MediaBrowser.XbmcMetadata.Providers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ServiceStack;
|
||||
using ServiceStack.Text.Jsv;
|
||||
using StringExtensions = MediaBrowser.Controller.Extensions.StringExtensions;
|
||||
using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate;
|
||||
using UtfUnknown;
|
||||
|
||||
namespace Emby.Server.Implementations
|
||||
{
|
||||
@@ -142,7 +137,7 @@ namespace Emby.Server.Implementations
|
||||
return false;
|
||||
}
|
||||
|
||||
if (StartupOptions.ContainsOption("-service"))
|
||||
if (StartupOptions.IsService)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -244,8 +239,6 @@ namespace Emby.Server.Implementations
|
||||
/// </summary>
|
||||
protected readonly SimpleInjector.Container Container = new SimpleInjector.Container();
|
||||
|
||||
protected ISystemEvents SystemEvents { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server configuration manager.
|
||||
/// </summary>
|
||||
@@ -305,7 +298,7 @@ namespace Emby.Server.Implementations
|
||||
|
||||
private ILiveTvManager LiveTvManager { get; set; }
|
||||
|
||||
public ILocalizationManager LocalizationManager { get; set; }
|
||||
public LocalizationManager LocalizationManager { get; set; }
|
||||
|
||||
private IEncodingManager EncodingManager { get; set; }
|
||||
private IChannelManager ChannelManager { get; set; }
|
||||
@@ -317,7 +310,7 @@ namespace Emby.Server.Implementations
|
||||
private IUserDataManager UserDataManager { get; set; }
|
||||
private IUserRepository UserRepository { get; set; }
|
||||
internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; }
|
||||
internal IItemRepository ItemRepository { get; set; }
|
||||
internal SqliteItemRepository ItemRepository { get; set; }
|
||||
|
||||
private INotificationManager NotificationManager { get; set; }
|
||||
private ISubtitleManager SubtitleManager { get; set; }
|
||||
@@ -346,12 +339,11 @@ namespace Emby.Server.Implementations
|
||||
protected IHttpResultFactory HttpResultFactory { get; private set; }
|
||||
protected IAuthService AuthService { get; private set; }
|
||||
|
||||
public StartupOptions StartupOptions { get; private set; }
|
||||
public IStartupOptions StartupOptions { get; private set; }
|
||||
|
||||
internal IImageEncoder ImageEncoder { get; private set; }
|
||||
|
||||
protected IProcessFactory ProcessFactory { get; private set; }
|
||||
protected ITimerFactory TimerFactory { get; private set; }
|
||||
protected ICryptoProvider CryptographyProvider = new CryptographyProvider();
|
||||
protected readonly IXmlSerializer XmlSerializer;
|
||||
|
||||
@@ -367,11 +359,10 @@ namespace Emby.Server.Implementations
|
||||
/// </summary>
|
||||
public ApplicationHost(ServerApplicationPaths applicationPaths,
|
||||
ILoggerFactory loggerFactory,
|
||||
StartupOptions options,
|
||||
IStartupOptions options,
|
||||
IFileSystem fileSystem,
|
||||
IEnvironmentInfo environmentInfo,
|
||||
IImageEncoder imageEncoder,
|
||||
ISystemEvents systemEvents,
|
||||
INetworkManager networkManager)
|
||||
{
|
||||
|
||||
@@ -383,7 +374,6 @@ namespace Emby.Server.Implementations
|
||||
NetworkManager = networkManager;
|
||||
networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
|
||||
EnvironmentInfo = environmentInfo;
|
||||
SystemEvents = systemEvents;
|
||||
|
||||
ApplicationPaths = applicationPaths;
|
||||
LoggerFactory = loggerFactory;
|
||||
@@ -466,9 +456,8 @@ namespace Emby.Server.Implementations
|
||||
private static Tuple<Assembly, string> GetAssembly(Type type)
|
||||
{
|
||||
var assembly = type.GetTypeInfo().Assembly;
|
||||
string path = null;
|
||||
|
||||
return new Tuple<Assembly, string>(assembly, path);
|
||||
return new Tuple<Assembly, string>(assembly, null);
|
||||
}
|
||||
|
||||
public virtual IStreamHelper CreateStreamHelper()
|
||||
@@ -575,7 +564,7 @@ namespace Emby.Server.Implementations
|
||||
{
|
||||
try
|
||||
{
|
||||
var assembly = Assembly.Load(File.ReadAllBytes(file));
|
||||
var assembly = Assembly.LoadFrom(file);
|
||||
|
||||
return new Tuple<Assembly, string>(assembly, file);
|
||||
}
|
||||
@@ -652,8 +641,10 @@ namespace Emby.Server.Implementations
|
||||
/// <summary>
|
||||
/// Runs the startup tasks.
|
||||
/// </summary>
|
||||
public Task RunStartupTasks()
|
||||
public async Task RunStartupTasks()
|
||||
{
|
||||
Logger.LogInformation("Running startup tasks");
|
||||
|
||||
Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false));
|
||||
|
||||
ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
|
||||
@@ -672,20 +663,20 @@ namespace Emby.Server.Implementations
|
||||
Logger.LogInformation("ServerId: {0}", SystemId);
|
||||
|
||||
var entryPoints = GetExports<IServerEntryPoint>();
|
||||
RunEntryPoints(entryPoints, true);
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
await Task.WhenAll(StartEntryPoints(entryPoints, true));
|
||||
Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:fff} ms", DateTime.Now - now);
|
||||
|
||||
Logger.LogInformation("Core startup complete");
|
||||
HttpServer.GlobalResponse = null;
|
||||
|
||||
Logger.LogInformation("Post-init migrations complete");
|
||||
|
||||
RunEntryPoints(entryPoints, false);
|
||||
Logger.LogInformation("All entry points have started");
|
||||
|
||||
return Task.CompletedTask;
|
||||
now = DateTime.UtcNow;
|
||||
await Task.WhenAll(StartEntryPoints(entryPoints, false));
|
||||
Logger.LogInformation("Executed all post-startup entry points in {Elapsed:fff} ms", DateTime.Now - now);
|
||||
}
|
||||
|
||||
private void RunEntryPoints(IEnumerable<IServerEntryPoint> entryPoints, bool isBeforeStartup)
|
||||
private IEnumerable<Task> StartEntryPoints(IEnumerable<IServerEntryPoint> entryPoints, bool isBeforeStartup)
|
||||
{
|
||||
foreach (var entryPoint in entryPoints)
|
||||
{
|
||||
@@ -694,22 +685,13 @@ namespace Emby.Server.Implementations
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = entryPoint.GetType().FullName;
|
||||
Logger.LogInformation("Starting entry point {Name}", name);
|
||||
var now = DateTime.UtcNow;
|
||||
try
|
||||
{
|
||||
entryPoint.Run();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error while running entrypoint {Name}", name);
|
||||
}
|
||||
Logger.LogInformation("Entry point completed: {Name}. Duration: {Duration} seconds", name, (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture), "ImageInfos");
|
||||
Logger.LogDebug("Starting entry point {Type}", entryPoint.GetType());
|
||||
|
||||
yield return entryPoint.RunAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task InitAsync()
|
||||
public async Task Init()
|
||||
{
|
||||
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
|
||||
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
|
||||
@@ -739,7 +721,7 @@ namespace Emby.Server.Implementations
|
||||
|
||||
SetHttpLimit();
|
||||
|
||||
await RegisterResourcesAsync();
|
||||
await RegisterResources();
|
||||
|
||||
FindParts();
|
||||
}
|
||||
@@ -754,7 +736,7 @@ namespace Emby.Server.Implementations
|
||||
/// <summary>
|
||||
/// Registers resources that classes will depend on
|
||||
/// </summary>
|
||||
protected async Task RegisterResourcesAsync()
|
||||
protected async Task RegisterResources()
|
||||
{
|
||||
RegisterSingleInstance(ConfigurationManager);
|
||||
RegisterSingleInstance<IApplicationHost>(this);
|
||||
@@ -762,7 +744,6 @@ namespace Emby.Server.Implementations
|
||||
RegisterSingleInstance<IApplicationPaths>(ApplicationPaths);
|
||||
|
||||
RegisterSingleInstance(JsonSerializer);
|
||||
RegisterSingleInstance(SystemEvents);
|
||||
|
||||
RegisterSingleInstance(LoggerFactory, false);
|
||||
RegisterSingleInstance(Logger);
|
||||
@@ -779,7 +760,7 @@ namespace Emby.Server.Implementations
|
||||
IsoManager = new IsoManager();
|
||||
RegisterSingleInstance(IsoManager);
|
||||
|
||||
TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager, SystemEvents);
|
||||
TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager);
|
||||
RegisterSingleInstance(TaskManager);
|
||||
|
||||
RegisterSingleInstance(XmlSerializer);
|
||||
@@ -787,9 +768,6 @@ namespace Emby.Server.Implementations
|
||||
ProcessFactory = new ProcessFactory();
|
||||
RegisterSingleInstance(ProcessFactory);
|
||||
|
||||
TimerFactory = new TimerFactory();
|
||||
RegisterSingleInstance(TimerFactory);
|
||||
|
||||
var streamHelper = CreateStreamHelper();
|
||||
ApplicationHost.StreamHelper = streamHelper;
|
||||
RegisterSingleInstance(streamHelper);
|
||||
@@ -799,12 +777,12 @@ namespace Emby.Server.Implementations
|
||||
SocketFactory = new SocketFactory();
|
||||
RegisterSingleInstance(SocketFactory);
|
||||
|
||||
InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, PackageRuntime);
|
||||
RegisterSingleInstance(InstallationManager);
|
||||
|
||||
ZipClient = new ZipClient(FileSystemManager);
|
||||
RegisterSingleInstance(ZipClient);
|
||||
|
||||
InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, ZipClient, PackageRuntime);
|
||||
RegisterSingleInstance(InstallationManager);
|
||||
|
||||
HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor());
|
||||
RegisterSingleInstance(HttpResultFactory);
|
||||
|
||||
@@ -816,9 +794,9 @@ namespace Emby.Server.Implementations
|
||||
IAssemblyInfo assemblyInfo = new AssemblyInfo();
|
||||
RegisterSingleInstance(assemblyInfo);
|
||||
|
||||
LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory, assemblyInfo, new TextLocalizer());
|
||||
StringExtensions.LocalizationManager = LocalizationManager;
|
||||
RegisterSingleInstance(LocalizationManager);
|
||||
LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory);
|
||||
await LocalizationManager.LoadAll();
|
||||
RegisterSingleInstance<ILocalizationManager>(LocalizationManager);
|
||||
|
||||
BlurayExaminer = new BdInfoExaminer(FileSystemManager);
|
||||
RegisterSingleInstance(BlurayExaminer);
|
||||
@@ -836,9 +814,8 @@ namespace Emby.Server.Implementations
|
||||
DisplayPreferencesRepository = displayPreferencesRepo;
|
||||
RegisterSingleInstance(DisplayPreferencesRepository);
|
||||
|
||||
var itemRepo = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo, FileSystemManager, EnvironmentInfo, TimerFactory);
|
||||
ItemRepository = itemRepo;
|
||||
RegisterSingleInstance(ItemRepository);
|
||||
ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo);
|
||||
RegisterSingleInstance<IItemRepository>(ItemRepository);
|
||||
|
||||
AuthenticationRepository = GetAuthenticationRepository();
|
||||
RegisterSingleInstance(AuthenticationRepository);
|
||||
@@ -853,7 +830,7 @@ namespace Emby.Server.Implementations
|
||||
var musicManager = new MusicManager(LibraryManager);
|
||||
RegisterSingleInstance<IMusicManager>(new MusicManager(LibraryManager));
|
||||
|
||||
LibraryMonitor = new LibraryMonitor(LoggerFactory, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, TimerFactory, SystemEvents, EnvironmentInfo);
|
||||
LibraryMonitor = new LibraryMonitor(LoggerFactory, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo);
|
||||
RegisterSingleInstance(LibraryMonitor);
|
||||
|
||||
RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LoggerFactory, LibraryManager, UserManager));
|
||||
@@ -885,10 +862,10 @@ namespace Emby.Server.Implementations
|
||||
DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LoggerFactory, NetworkManager);
|
||||
RegisterSingleInstance(DeviceManager);
|
||||
|
||||
MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, TimerFactory, () => MediaEncoder);
|
||||
MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder);
|
||||
RegisterSingleInstance(MediaSourceManager);
|
||||
|
||||
SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, ServerConfigurationManager, LocalizationManager);
|
||||
SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager);
|
||||
RegisterSingleInstance(SubtitleManager);
|
||||
|
||||
ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, LoggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer);
|
||||
@@ -900,7 +877,7 @@ namespace Emby.Server.Implementations
|
||||
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager);
|
||||
RegisterSingleInstance(ChannelManager);
|
||||
|
||||
SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager, TimerFactory);
|
||||
SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager);
|
||||
RegisterSingleInstance(SessionManager);
|
||||
|
||||
var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo);
|
||||
@@ -912,7 +889,7 @@ namespace Emby.Server.Implementations
|
||||
PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager);
|
||||
RegisterSingleInstance(PlaylistManager);
|
||||
|
||||
LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, ProviderManager, FileSystemManager, () => ChannelManager);
|
||||
LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager);
|
||||
RegisterSingleInstance(LiveTvManager);
|
||||
|
||||
UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
|
||||
@@ -921,7 +898,7 @@ namespace Emby.Server.Implementations
|
||||
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
|
||||
RegisterSingleInstance(NotificationManager);
|
||||
|
||||
RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory, TimerFactory));
|
||||
RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory));
|
||||
|
||||
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
|
||||
RegisterSingleInstance(ChapterManager);
|
||||
@@ -931,7 +908,7 @@ namespace Emby.Server.Implementations
|
||||
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
|
||||
RegisterSingleInstance(EncodingManager);
|
||||
|
||||
var activityLogRepo = await GetActivityLogRepositoryAsync();
|
||||
var activityLogRepo = GetActivityLogRepository();
|
||||
RegisterSingleInstance(activityLogRepo);
|
||||
RegisterSingleInstance<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
|
||||
|
||||
@@ -956,7 +933,7 @@ namespace Emby.Server.Implementations
|
||||
((UserManager)UserManager).Initialize();
|
||||
|
||||
((UserDataManager)UserDataManager).Repository = userDataRepo;
|
||||
itemRepo.Initialize(userDataRepo, UserManager);
|
||||
ItemRepository.Initialize(userDataRepo, UserManager);
|
||||
((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
|
||||
}
|
||||
|
||||
@@ -1015,7 +992,7 @@ namespace Emby.Server.Implementations
|
||||
|
||||
try
|
||||
{
|
||||
if (!FileSystemManager.FileExists(certificateLocation))
|
||||
if (!File.Exists(certificateLocation))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -1146,11 +1123,11 @@ namespace Emby.Server.Implementations
|
||||
return repo;
|
||||
}
|
||||
|
||||
private async Task<IActivityRepository> GetActivityLogRepositoryAsync()
|
||||
private IActivityRepository GetActivityLogRepository()
|
||||
{
|
||||
var repo = new ActivityRepository(ServerConfigurationManager.ApplicationPaths.DataPath);
|
||||
var repo = new ActivityRepository(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager);
|
||||
|
||||
await repo.Database.EnsureCreatedAsync();
|
||||
repo.Initialize();
|
||||
|
||||
return repo;
|
||||
}
|
||||
@@ -1441,7 +1418,7 @@ namespace Emby.Server.Implementations
|
||||
|
||||
//if (generateCertificate)
|
||||
//{
|
||||
// if (!FileSystemManager.FileExists(certPath))
|
||||
// if (!File.Exists(certPath))
|
||||
// {
|
||||
// FileSystemManager.CreateDirectory(FileSystemManager.GetDirectoryName(certPath));
|
||||
|
||||
@@ -1571,7 +1548,7 @@ namespace Emby.Server.Implementations
|
||||
/// <returns>IEnumerable{Assembly}.</returns>
|
||||
protected List<Tuple<Assembly, string>> GetComposablePartAssemblies()
|
||||
{
|
||||
var list = GetPluginAssemblies();
|
||||
var list = GetPluginAssemblies(ApplicationPaths.PluginsPath);
|
||||
|
||||
// Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that
|
||||
// This will prevent the .dll file from getting locked, and allow us to replace it when needed
|
||||
@@ -1622,84 +1599,11 @@ namespace Emby.Server.Implementations
|
||||
|
||||
protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin assemblies.
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{Assembly}.</returns>
|
||||
private List<Tuple<Assembly, string>> GetPluginAssemblies()
|
||||
{
|
||||
// Copy pre-installed plugins
|
||||
var sourcePath = Path.Combine(ApplicationPaths.ApplicationResourcesPath, "plugins");
|
||||
CopyPlugins(sourcePath, ApplicationPaths.PluginsPath);
|
||||
|
||||
return GetPluginAssemblies(ApplicationPaths.PluginsPath);
|
||||
}
|
||||
|
||||
private void CopyPlugins(string source, string target)
|
||||
{
|
||||
List<string> files;
|
||||
|
||||
try
|
||||
{
|
||||
files = Directory.EnumerateFiles(source, "*.dll", SearchOption.TopDirectoryOnly)
|
||||
.ToList();
|
||||
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (files.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var sourceFile in files)
|
||||
{
|
||||
var filename = Path.GetFileName(sourceFile);
|
||||
var targetFile = Path.Combine(target, filename);
|
||||
|
||||
var targetFileExists = File.Exists(targetFile);
|
||||
|
||||
if (!targetFileExists && ServerConfigurationManager.Configuration.UninstalledPlugins.Contains(filename, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (targetFileExists && GetDllVersion(targetFile) >= GetDllVersion(sourceFile))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(target);
|
||||
File.Copy(sourceFile, targetFile, true);
|
||||
}
|
||||
}
|
||||
|
||||
private Version GetDllVersion(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = Version.Parse(FileVersionInfo.GetVersionInfo(path).FileVersion);
|
||||
|
||||
Logger.LogInformation("File {Path} has version {Version}", path, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error getting version number from {Path}", path);
|
||||
|
||||
return new Version(1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Tuple<Assembly, string>> GetPluginAssemblies(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return FilterAssembliesToLoad(Directory.EnumerateFiles(path, "*.dll", SearchOption.TopDirectoryOnly))
|
||||
return FilterAssembliesToLoad(Directory.EnumerateFiles(path, "*.dll", SearchOption.AllDirectories))
|
||||
.Select(LoadAssembly)
|
||||
.Where(a => a != null)
|
||||
.ToList();
|
||||
@@ -1752,7 +1656,6 @@ namespace Emby.Server.Implementations
|
||||
|
||||
var minRequiredVersions = new Dictionary<string, Version>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "GameBrowser.dll", new Version(3, 1) },
|
||||
{ "moviethemesongs.dll", new Version(1, 6) },
|
||||
{ "themesongs.dll", new Version(1, 2) }
|
||||
};
|
||||
@@ -1828,7 +1731,7 @@ namespace Emby.Server.Implementations
|
||||
EncoderLocationType = MediaEncoder.EncoderLocationType,
|
||||
SystemArchitecture = EnvironmentInfo.SystemArchitecture,
|
||||
SystemUpdateLevel = SystemUpdateLevel,
|
||||
PackageName = StartupOptions.GetOption("-package")
|
||||
PackageName = StartupOptions.PackageName
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Archiving
|
||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
||||
{
|
||||
using (var fileStream = _fileSystem.OpenRead(sourceFile))
|
||||
using (var fileStream = File.OpenRead(sourceFile))
|
||||
{
|
||||
ExtractAll(fileStream, targetPath, overwriteExistingFiles);
|
||||
}
|
||||
@@ -115,7 +115,7 @@ namespace Emby.Server.Implementations.Archiving
|
||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
||||
{
|
||||
using (var fileStream = _fileSystem.OpenRead(sourceFile))
|
||||
using (var fileStream = File.OpenRead(sourceFile))
|
||||
{
|
||||
ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
|
||||
}
|
||||
@@ -155,7 +155,7 @@ namespace Emby.Server.Implementations.Archiving
|
||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
||||
{
|
||||
using (var fileStream = _fileSystem.OpenRead(sourceFile))
|
||||
using (var fileStream = File.OpenRead(sourceFile))
|
||||
{
|
||||
ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
|
||||
}
|
||||
|
||||
@@ -355,7 +355,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
return;
|
||||
}
|
||||
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
_jsonSerializer.SerializeToFile(mediaSources, path);
|
||||
}
|
||||
@@ -834,7 +834,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
{
|
||||
try
|
||||
{
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
_jsonSerializer.SerializeToFile(result, path);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@ namespace Emby.Server.Implementations.Collections
|
||||
return null;
|
||||
})
|
||||
.Where(i => i != null)
|
||||
.DistinctBy(i => i.Id)
|
||||
.GroupBy(x => x.Id)
|
||||
.Select(x => x.First())
|
||||
.OrderBy(i => Guid.NewGuid())
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
return null;
|
||||
}
|
||||
|
||||
_fileSystem.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
var libraryOptions = new LibraryOptions
|
||||
{
|
||||
@@ -133,7 +133,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
|
||||
try
|
||||
{
|
||||
_fileSystem.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
var collection = new BoxSet
|
||||
{
|
||||
@@ -353,13 +353,13 @@ namespace Emby.Server.Implementations.Collections
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async void Run()
|
||||
public async Task RunAsync()
|
||||
{
|
||||
if (!_config.Configuration.CollectionsUpgraded && _config.Configuration.IsStartupWizardCompleted)
|
||||
{
|
||||
var path = _collectionManager.GetCollectionsFolderPath();
|
||||
|
||||
if (_fileSystem.DirectoryExists(path))
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace Emby.Server.Implementations.Configuration
|
||||
&& !string.Equals(Configuration.CertificatePath ?? string.Empty, newPath))
|
||||
{
|
||||
// Validate
|
||||
if (!FileSystem.FileExists(newPath))
|
||||
if (!File.Exists(newPath))
|
||||
{
|
||||
throw new FileNotFoundException(string.Format("Certificate file '{0}' does not exist.", newPath));
|
||||
}
|
||||
@@ -168,7 +168,7 @@ namespace Emby.Server.Implementations.Configuration
|
||||
&& !string.Equals(Configuration.MetadataPath ?? string.Empty, newPath))
|
||||
{
|
||||
// Validate
|
||||
if (!FileSystem.DirectoryExists(newPath))
|
||||
if (!Directory.Exists(newPath))
|
||||
{
|
||||
throw new FileNotFoundException(string.Format("{0} does not exist.", newPath));
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -53,11 +53,11 @@ namespace Emby.Server.Implementations.Devices
|
||||
{
|
||||
var path = CachePath;
|
||||
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
lock (_syncLock)
|
||||
{
|
||||
_fileSystem.WriteAllText(path, id, Encoding.UTF8);
|
||||
File.WriteAllText(path, id, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Emby.Server.Implementations.Devices
|
||||
public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
|
||||
{
|
||||
var path = Path.Combine(GetDevicePath(deviceId), "capabilities.json");
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
lock (_capabilitiesSyncLock)
|
||||
{
|
||||
@@ -239,7 +239,7 @@ namespace Emby.Server.Implementations.Devices
|
||||
path = Path.Combine(path, file.Name);
|
||||
path = Path.ChangeExtension(path, MimeTypes.ToExtension(file.MimeType) ?? "jpg");
|
||||
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
await EnsureLibraryFolder(uploadPathInfo.Item2, uploadPathInfo.Item3).ConfigureAwait(false);
|
||||
|
||||
@@ -275,7 +275,7 @@ namespace Emby.Server.Implementations.Devices
|
||||
private void AddCameraUpload(string deviceId, LocalFileInfo file)
|
||||
{
|
||||
var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
lock (_cameraUploadSyncLock)
|
||||
{
|
||||
@@ -317,7 +317,7 @@ namespace Emby.Server.Implementations.Devices
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
_fileSystem.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
var libraryOptions = new LibraryOptions
|
||||
{
|
||||
@@ -425,13 +425,13 @@ namespace Emby.Server.Implementations.Devices
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async void Run()
|
||||
public async Task RunAsync()
|
||||
{
|
||||
if (!_config.Configuration.CameraUploadUpgraded && _config.Configuration.IsStartupWizardCompleted)
|
||||
{
|
||||
var path = _deviceManager.GetUploadsPath();
|
||||
|
||||
if (_fileSystem.DirectoryExists(path))
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace Emby.Server.Implementations.Diagnostics
|
||||
{
|
||||
return _process.WaitForExit(timeMs);
|
||||
}
|
||||
|
||||
|
||||
public Task<bool> WaitForExitAsync(int timeMs)
|
||||
{
|
||||
//Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
|
||||
|
||||
@@ -374,10 +374,6 @@ namespace Emby.Server.Implementations.Dto
|
||||
dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
|
||||
dto.SongCount = taggedItems.Count(i => i is Audio);
|
||||
}
|
||||
else if (item is GameGenre)
|
||||
{
|
||||
dto.GameCount = taggedItems.Count(i => i is Game);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This populates them all and covers Genre, Person, Studio, Year
|
||||
@@ -385,7 +381,6 @@ namespace Emby.Server.Implementations.Dto
|
||||
dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
|
||||
dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
|
||||
dto.EpisodeCount = taggedItems.Count(i => i is Episode);
|
||||
dto.GameCount = taggedItems.Count(i => i is Game);
|
||||
dto.MovieCount = taggedItems.Count(i => i is Movie);
|
||||
dto.TrailerCount = taggedItems.Count(i => i is Trailer);
|
||||
dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
|
||||
@@ -532,17 +527,6 @@ namespace Emby.Server.Implementations.Dto
|
||||
dto.Album = item.Album;
|
||||
}
|
||||
|
||||
private static void SetGameProperties(BaseItemDto dto, Game item)
|
||||
{
|
||||
dto.GameSystem = item.GameSystem;
|
||||
dto.MultiPartGameFiles = item.MultiPartGameFiles;
|
||||
}
|
||||
|
||||
private static void SetGameSystemProperties(BaseItemDto dto, GameSystem item)
|
||||
{
|
||||
dto.GameSystem = item.GameSystemName;
|
||||
}
|
||||
|
||||
private string[] GetImageTags(BaseItem item, List<ItemImageInfo> images)
|
||||
{
|
||||
return images
|
||||
@@ -636,7 +620,8 @@ namespace Emby.Server.Implementations.Dto
|
||||
}
|
||||
|
||||
}).Where(i => i != null)
|
||||
.DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(x => x.First())
|
||||
.ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
for (var i = 0; i < people.Count; i++)
|
||||
@@ -698,11 +683,6 @@ namespace Emby.Server.Implementations.Dto
|
||||
return _libraryManager.GetMusicGenreId(name);
|
||||
}
|
||||
|
||||
if (owner is Game || owner is GameSystem)
|
||||
{
|
||||
return _libraryManager.GetGameGenreId(name);
|
||||
}
|
||||
|
||||
return _libraryManager.GetGenreId(name);
|
||||
}
|
||||
|
||||
@@ -1206,20 +1186,6 @@ namespace Emby.Server.Implementations.Dto
|
||||
}
|
||||
}
|
||||
|
||||
var game = item as Game;
|
||||
|
||||
if (game != null)
|
||||
{
|
||||
SetGameProperties(dto, game);
|
||||
}
|
||||
|
||||
var gameSystem = item as GameSystem;
|
||||
|
||||
if (gameSystem != null)
|
||||
{
|
||||
SetGameSystemProperties(dto, gameSystem);
|
||||
}
|
||||
|
||||
var musicVideo = item as MusicVideo;
|
||||
if (musicVideo != null)
|
||||
{
|
||||
@@ -1428,7 +1394,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
var supportedEnhancers = _imageProcessor.GetSupportedEnhancers(item, ImageType.Primary);
|
||||
|
||||
ImageSize size;
|
||||
ImageDimensions size;
|
||||
|
||||
var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
|
||||
|
||||
@@ -1439,9 +1405,9 @@ namespace Emby.Server.Implementations.Dto
|
||||
return defaultAspectRatio;
|
||||
}
|
||||
|
||||
double dummyWidth = 200;
|
||||
double dummyHeight = dummyWidth / defaultAspectRatio;
|
||||
size = new ImageSize(dummyWidth, dummyHeight);
|
||||
int dummyWidth = 200;
|
||||
int dummyHeight = Convert.ToInt32(dummyWidth / defaultAspectRatio);
|
||||
size = new ImageDimensions(dummyWidth, dummyHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1452,7 +1418,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
try
|
||||
{
|
||||
size = _imageProcessor.GetImageSize(item, imageInfo);
|
||||
size = _imageProcessor.GetImageDimensions(item, imageInfo);
|
||||
|
||||
if (size.Width <= 0 || size.Height <= 0)
|
||||
{
|
||||
@@ -1481,7 +1447,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
var width = size.Width;
|
||||
var height = size.Height;
|
||||
|
||||
if (width.Equals(0) || height.Equals(0))
|
||||
if (width <= 0 || height <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
|
||||
@@ -22,12 +22,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" 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.core" Version="1.1.8" />
|
||||
<PackageReference Include="SQLitePCLRaw.core" Version="1.1.11" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="1.0.0" />
|
||||
<PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -44,7 +42,7 @@
|
||||
<EmbeddedResource Include="Localization\iso6392.txt" />
|
||||
<EmbeddedResource Include="Localization\countries.json" />
|
||||
<EmbeddedResource Include="Localization\Core\*.json" />
|
||||
<EmbeddedResource Include="Localization\Ratings\*.txt" />
|
||||
<EmbeddedResource Include="Localization\Ratings\*.csv" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -9,7 +9,6 @@ using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using MediaBrowser.Model.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
@@ -22,11 +21,10 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly ITimerFactory _timerFactory;
|
||||
|
||||
private ITimer _timer;
|
||||
private Timer _timer;
|
||||
|
||||
public AutomaticRestartEntryPoint(IServerApplicationHost appHost, ILogger logger, ITaskManager iTaskManager, ISessionManager sessionManager, IServerConfigurationManager config, ILiveTvManager liveTvManager, ITimerFactory timerFactory)
|
||||
public AutomaticRestartEntryPoint(IServerApplicationHost appHost, ILogger logger, ITaskManager iTaskManager, ISessionManager sessionManager, IServerConfigurationManager config, ILiveTvManager liveTvManager)
|
||||
{
|
||||
_appHost = appHost;
|
||||
_logger = logger;
|
||||
@@ -34,15 +32,16 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
_sessionManager = sessionManager;
|
||||
_config = config;
|
||||
_liveTvManager = liveTvManager;
|
||||
_timerFactory = timerFactory;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
public Task RunAsync()
|
||||
{
|
||||
if (_appHost.CanSelfRestart)
|
||||
{
|
||||
_appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
void _appHost_HasPendingRestartChanged(object sender, EventArgs e)
|
||||
@@ -51,7 +50,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
|
||||
if (_appHost.HasPendingRestart)
|
||||
{
|
||||
_timer = _timerFactory.Create(TimerCallback, null, TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(15));
|
||||
_timer = new Timer(TimerCallback, null, TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(15));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Mono.Nat;
|
||||
|
||||
@@ -24,19 +23,17 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||
|
||||
private ITimer _timer;
|
||||
private readonly ITimerFactory _timerFactory;
|
||||
private Timer _timer;
|
||||
|
||||
private NatManager _natManager;
|
||||
|
||||
public ExternalPortForwarding(ILoggerFactory loggerFactory, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, ITimerFactory timerFactory)
|
||||
public ExternalPortForwarding(ILoggerFactory loggerFactory, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger("PortMapper");
|
||||
_appHost = appHost;
|
||||
_config = config;
|
||||
_deviceDiscovery = deviceDiscovery;
|
||||
_httpClient = httpClient;
|
||||
_timerFactory = timerFactory;
|
||||
_config.ConfigurationUpdated += _config_ConfigurationUpdated1;
|
||||
}
|
||||
|
||||
@@ -61,17 +58,17 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
return string.Join("|", values.ToArray());
|
||||
}
|
||||
|
||||
void _config_ConfigurationUpdated(object sender, EventArgs e)
|
||||
private async void _config_ConfigurationUpdated(object sender, EventArgs e)
|
||||
{
|
||||
if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
DisposeNat();
|
||||
|
||||
Run();
|
||||
await RunAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public void Run()
|
||||
public Task RunAsync()
|
||||
{
|
||||
if (_config.Configuration.EnableUPnP && _config.Configuration.EnableRemoteAccess)
|
||||
{
|
||||
@@ -80,6 +77,8 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
|
||||
_config.ConfigurationUpdated -= _config_ConfigurationUpdated;
|
||||
_config.ConfigurationUpdated += _config_ConfigurationUpdated;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
@@ -92,7 +91,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
_natManager.StartDiscovery();
|
||||
}
|
||||
|
||||
_timer = _timerFactory.Create(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
|
||||
_timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
|
||||
|
||||
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
@@ -13,7 +14,6 @@ using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
@@ -28,7 +28,6 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ITimerFactory _timerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The _library changed sync lock
|
||||
@@ -46,7 +45,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
/// Gets or sets the library update timer.
|
||||
/// </summary>
|
||||
/// <value>The library update timer.</value>
|
||||
private ITimer LibraryUpdateTimer { get; set; }
|
||||
private Timer LibraryUpdateTimer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The library update duration
|
||||
@@ -55,17 +54,16 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
|
||||
private readonly IProviderManager _providerManager;
|
||||
|
||||
public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger, ITimerFactory timerFactory, IProviderManager providerManager)
|
||||
public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger, IProviderManager providerManager)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_sessionManager = sessionManager;
|
||||
_userManager = userManager;
|
||||
_logger = logger;
|
||||
_timerFactory = timerFactory;
|
||||
_providerManager = providerManager;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
public Task RunAsync()
|
||||
{
|
||||
_libraryManager.ItemAdded += libraryManager_ItemAdded;
|
||||
_libraryManager.ItemUpdated += libraryManager_ItemUpdated;
|
||||
@@ -74,6 +72,8 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
_providerManager.RefreshCompleted += _providerManager_RefreshCompleted;
|
||||
_providerManager.RefreshStarted += _providerManager_RefreshStarted;
|
||||
_providerManager.RefreshProgress += _providerManager_RefreshProgress;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Dictionary<Guid, DateTime> _lastProgressMessageTimes = new Dictionary<Guid, DateTime>();
|
||||
@@ -188,7 +188,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
if (LibraryUpdateTimer == null)
|
||||
{
|
||||
LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
|
||||
LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
|
||||
Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
@@ -222,7 +222,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
if (LibraryUpdateTimer == null)
|
||||
{
|
||||
LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
|
||||
LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
|
||||
Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
@@ -250,7 +250,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
if (LibraryUpdateTimer == null)
|
||||
{
|
||||
LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
|
||||
LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
|
||||
Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
@@ -277,14 +277,21 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
lock (_libraryChangedSyncLock)
|
||||
{
|
||||
// Remove dupes in case some were saved multiple times
|
||||
var foldersAddedTo = _foldersAddedTo.DistinctBy(i => i.Id).ToList();
|
||||
var foldersAddedTo = _foldersAddedTo
|
||||
.GroupBy(x => x.Id)
|
||||
.Select(x => x.First())
|
||||
.ToList();
|
||||
|
||||
var foldersRemovedFrom = _foldersRemovedFrom.DistinctBy(i => i.Id).ToList();
|
||||
var foldersRemovedFrom = _foldersRemovedFrom
|
||||
.GroupBy(x => x.Id)
|
||||
.Select(x => x.First())
|
||||
.ToList();
|
||||
|
||||
var itemsUpdated = _itemsUpdated
|
||||
.Where(i => !_itemsAdded.Contains(i))
|
||||
.DistinctBy(i => i.Id)
|
||||
.ToList();
|
||||
.Where(i => !_itemsAdded.Contains(i))
|
||||
.GroupBy(x => x.Id)
|
||||
.Select(x => x.First())
|
||||
.ToList();
|
||||
|
||||
SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
@@ -24,12 +25,14 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
_liveTvManager = liveTvManager;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
public Task RunAsync()
|
||||
{
|
||||
_liveTvManager.TimerCancelled += _liveTvManager_TimerCancelled;
|
||||
_liveTvManager.SeriesTimerCancelled += _liveTvManager_SeriesTimerCancelled;
|
||||
_liveTvManager.TimerCreated += _liveTvManager_TimerCreated;
|
||||
_liveTvManager.SeriesTimerCreated += _liveTvManager_SeriesTimerCreated;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void _liveTvManager_SeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Controller;
|
||||
@@ -49,7 +50,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
_sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
public Task RunAsync()
|
||||
{
|
||||
_userManager.UserDeleted += userManager_UserDeleted;
|
||||
_userManager.UserUpdated += userManager_UserUpdated;
|
||||
@@ -65,6 +66,8 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
_installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed;
|
||||
|
||||
_taskManager.TaskCompleted += _taskManager_TaskCompleted;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
void _installationManager_PackageInstalling(object sender, InstallationEventArgs e)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Browser;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
@@ -32,11 +33,11 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
/// <summary>
|
||||
/// Runs this instance.
|
||||
/// </summary>
|
||||
public void Run()
|
||||
public Task RunAsync()
|
||||
{
|
||||
if (!_appHost.CanLaunchWebBrowser)
|
||||
{
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (!_config.Configuration.IsStartupWizardCompleted)
|
||||
@@ -47,11 +48,13 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
var options = ((ApplicationHost)_appHost).StartupOptions;
|
||||
|
||||
if (!options.ContainsOption("-noautorunwebapp"))
|
||||
if (!options.NoAutoRunWebApp)
|
||||
{
|
||||
BrowserLauncher.OpenWebApp(_appHost);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
using System;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Model.System;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
public class SystemEvents : IServerEntryPoint
|
||||
{
|
||||
private readonly ISystemEvents _systemEvents;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
|
||||
public SystemEvents(ISystemEvents systemEvents, IServerApplicationHost appHost)
|
||||
{
|
||||
_systemEvents = systemEvents;
|
||||
_appHost = appHost;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
_systemEvents.SystemShutdown += _systemEvents_SystemShutdown;
|
||||
}
|
||||
|
||||
private void _systemEvents_SystemShutdown(object sender, EventArgs e)
|
||||
{
|
||||
_appHost.Shutdown();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_systemEvents.SystemShutdown -= _systemEvents_SystemShutdown;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Udp;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
@@ -43,7 +44,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
/// <summary>
|
||||
/// Runs this instance.
|
||||
/// </summary>
|
||||
public void Run()
|
||||
public Task RunAsync()
|
||||
{
|
||||
var udpServer = new UdpServer(_logger, _appHost, _json, _socketFactory);
|
||||
|
||||
@@ -57,6 +58,8 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
_logger.LogError(ex, "Failed to start UDP Server");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -10,7 +10,6 @@ using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Session;
|
||||
using MediaBrowser.Model.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
@@ -23,24 +22,24 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
private readonly object _syncLock = new object();
|
||||
private ITimer UpdateTimer { get; set; }
|
||||
private readonly ITimerFactory _timerFactory;
|
||||
private Timer UpdateTimer { get; set; }
|
||||
private const int UpdateDuration = 500;
|
||||
|
||||
private readonly Dictionary<Guid, List<BaseItem>> _changedItems = new Dictionary<Guid, List<BaseItem>>();
|
||||
|
||||
public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager, ITimerFactory timerFactory)
|
||||
public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager)
|
||||
{
|
||||
_userDataManager = userDataManager;
|
||||
_sessionManager = sessionManager;
|
||||
_logger = logger;
|
||||
_userManager = userManager;
|
||||
_timerFactory = timerFactory;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
public Task RunAsync()
|
||||
{
|
||||
_userDataManager.UserDataSaved += _userDataManager_UserDataSaved;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
void _userDataManager_UserDataSaved(object sender, UserDataSaveEventArgs e)
|
||||
@@ -54,7 +53,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
if (UpdateTimer == null)
|
||||
{
|
||||
UpdateTimer = _timerFactory.Create(UpdateTimerCallback, null, UpdateDuration,
|
||||
UpdateTimer = new Timer(UpdateTimerCallback, null, UpdateDuration,
|
||||
Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
@@ -121,7 +120,8 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
var user = _userManager.GetUserById(userId);
|
||||
|
||||
var dtoList = changedItems
|
||||
.DistinctBy(i => i.Id)
|
||||
.GroupBy(x => x.Id)
|
||||
.Select(x => x.First())
|
||||
.Select(i =>
|
||||
{
|
||||
var dto = _userDataManager.GetUserDataDto(i, user);
|
||||
|
||||
@@ -28,10 +28,10 @@ namespace Emby.Server.Implementations.FFMpeg
|
||||
_ffmpegInstallInfo = ffmpegInstallInfo;
|
||||
}
|
||||
|
||||
public FFMpegInfo GetFFMpegInfo(StartupOptions options)
|
||||
public FFMpegInfo GetFFMpegInfo(IStartupOptions options)
|
||||
{
|
||||
var customffMpegPath = options.GetOption("-ffmpeg");
|
||||
var customffProbePath = options.GetOption("-ffprobe");
|
||||
var customffMpegPath = options.FFmpegPath;
|
||||
var customffProbePath = options.FFprobePath;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(customffMpegPath) && !string.IsNullOrWhiteSpace(customffProbePath))
|
||||
{
|
||||
@@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.FFMpeg
|
||||
var prebuiltFolder = _appPaths.ProgramSystemPath;
|
||||
var prebuiltffmpeg = Path.Combine(prebuiltFolder, downloadInfo.FFMpegFilename);
|
||||
var prebuiltffprobe = Path.Combine(prebuiltFolder, downloadInfo.FFProbeFilename);
|
||||
if (_fileSystem.FileExists(prebuiltffmpeg) && _fileSystem.FileExists(prebuiltffprobe))
|
||||
if (File.Exists(prebuiltffmpeg) && File.Exists(prebuiltffprobe))
|
||||
{
|
||||
return new FFMpegInfo
|
||||
{
|
||||
@@ -75,11 +75,11 @@ namespace Emby.Server.Implementations.FFMpeg
|
||||
Version = version
|
||||
};
|
||||
|
||||
_fileSystem.CreateDirectory(versionedDirectoryPath);
|
||||
Directory.CreateDirectory(versionedDirectoryPath);
|
||||
|
||||
var excludeFromDeletions = new List<string> { versionedDirectoryPath };
|
||||
|
||||
if (!_fileSystem.FileExists(info.ProbePath) || !_fileSystem.FileExists(info.EncoderPath))
|
||||
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);
|
||||
@@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.FFMpeg
|
||||
else
|
||||
{
|
||||
info = existingVersion;
|
||||
versionedDirectoryPath = _fileSystem.GetDirectoryName(info.EncoderPath);
|
||||
versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath);
|
||||
excludeFromDeletions.Add(versionedDirectoryPath);
|
||||
}
|
||||
}
|
||||
@@ -130,7 +130,7 @@ namespace Emby.Server.Implementations.FFMpeg
|
||||
{
|
||||
EncoderPath = encoder,
|
||||
ProbePath = probe,
|
||||
Version = Path.GetFileName(_fileSystem.GetDirectoryName(probe))
|
||||
Version = Path.GetFileName(Path.GetDirectoryName(probe))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,11 +66,6 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||
|
||||
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
|
||||
ServicePointManager.Expect100Continue = false;
|
||||
|
||||
#if NET46
|
||||
// Trakt requests sometimes fail without this
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,23 +101,6 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||
return client;
|
||||
}
|
||||
|
||||
private static WebRequest CreateWebRequest(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
return WebRequest.Create(url);
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
//Webrequest creation does fail on MONO randomly when using WebRequest.Create
|
||||
//the issue occurs in the GetCreator method here: http://www.oschina.net/code/explore/mono-2.8.1/mcs/class/System/System.Net/WebRequest.cs
|
||||
|
||||
var type = Type.GetType("System.Net.HttpRequestCreator, System, Version=4.0.0.0,Culture=neutral, PublicKeyToken=b77a5c561934e089");
|
||||
var creator = Activator.CreateInstance(type, nonPublic: true) as IWebRequestCreate;
|
||||
return creator.Create(new Uri(url)) as HttpWebRequest;
|
||||
}
|
||||
}
|
||||
|
||||
private WebRequest GetRequest(HttpRequestOptions options, string method)
|
||||
{
|
||||
string url = options.Url;
|
||||
@@ -135,7 +113,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||
url = url.Replace(userInfo + "@", string.Empty);
|
||||
}
|
||||
|
||||
var request = CreateWebRequest(url);
|
||||
var request = WebRequest.Create(url);
|
||||
|
||||
if (request is HttpWebRequest httpWebRequest)
|
||||
{
|
||||
@@ -264,7 +242,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||
}
|
||||
|
||||
var url = options.Url;
|
||||
var urlHash = url.ToLower().GetMD5().ToString("N");
|
||||
var urlHash = url.ToLowerInvariant().GetMD5().ToString("N");
|
||||
|
||||
var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash);
|
||||
|
||||
@@ -286,28 +264,18 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||
|
||||
private HttpResponseInfo GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url)
|
||||
{
|
||||
try
|
||||
if (File.Exists(responseCachePath)
|
||||
&& _fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
|
||||
{
|
||||
if (_fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
|
||||
var stream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true);
|
||||
|
||||
return new HttpResponseInfo
|
||||
{
|
||||
var stream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true);
|
||||
|
||||
return new HttpResponseInfo
|
||||
{
|
||||
ResponseUrl = url,
|
||||
Content = stream,
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
ContentLength = stream.Length
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException) // REVIEW: @bond Is this really faster?
|
||||
{
|
||||
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
|
||||
ResponseUrl = url,
|
||||
Content = stream,
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
ContentLength = stream.Length
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -315,7 +283,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||
|
||||
private async Task CacheResponse(HttpResponseInfo response, string responseCachePath)
|
||||
{
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(responseCachePath));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(responseCachePath));
|
||||
|
||||
using (var fileStream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.None, true))
|
||||
{
|
||||
@@ -384,11 +352,11 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||
{
|
||||
if (options.LogRequestAsDebug)
|
||||
{
|
||||
_logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToUpper(), options.Url);
|
||||
_logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("HttpClientManager {0}: {1}", httpMethod.ToUpper(), options.Url);
|
||||
_logger.LogInformation("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,7 +491,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||
{
|
||||
ValidateParams(options);
|
||||
|
||||
_fileSystem.CreateDirectory(_appPaths.TempDirectory);
|
||||
Directory.CreateDirectory(_appPaths.TempDirectory);
|
||||
|
||||
var tempFile = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp");
|
||||
|
||||
@@ -637,14 +605,16 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||
|
||||
var exception = new HttpException(webException.Message, webException);
|
||||
|
||||
var response = webException.Response as HttpWebResponse;
|
||||
if (response != null)
|
||||
using (var response = webException.Response as HttpWebResponse)
|
||||
{
|
||||
exception.StatusCode = response.StatusCode;
|
||||
|
||||
if ((int)response.StatusCode == 429)
|
||||
if (response != null)
|
||||
{
|
||||
client.LastTimeout = DateTime.UtcNow;
|
||||
exception.StatusCode = response.StatusCode;
|
||||
|
||||
if ((int)response.StatusCode == 429)
|
||||
{
|
||||
client.LastTimeout = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -394,7 +394,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
{
|
||||
return contentType == null
|
||||
? null
|
||||
: contentType.Split(';')[0].ToLower().Trim();
|
||||
: contentType.Split(';')[0].ToLowerInvariant().Trim();
|
||||
}
|
||||
|
||||
private static string SerializeToXmlString(object from)
|
||||
@@ -422,18 +422,20 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// <summary>
|
||||
/// Pres the process optimized result.
|
||||
/// </summary>
|
||||
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
|
||||
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;
|
||||
AddCachingHeaders(responseHeaders, options.CacheDuration, noCache, options.DateLastModified);
|
||||
|
||||
if (!noCache)
|
||||
{
|
||||
if (IsNotModified(requestContext, cacheKey))
|
||||
{
|
||||
AddAgeHeader(responseHeaders, lastDateModified);
|
||||
AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration);
|
||||
DateTime.TryParse(requestContext.Headers.Get("If-Modified-Since"), out var ifModifiedSinceHeader);
|
||||
|
||||
var result = new HttpResult(Array.Empty<byte>(), contentType ?? "text/html", HttpStatusCode.NotModified);
|
||||
if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified))
|
||||
{
|
||||
AddAgeHeader(responseHeaders, options.DateLastModified);
|
||||
|
||||
var result = new HttpResult(Array.Empty<byte>(), options.ContentType ?? "text/html", HttpStatusCode.NotModified);
|
||||
|
||||
AddResponseHeaders(result, responseHeaders);
|
||||
|
||||
@@ -441,8 +443,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
}
|
||||
|
||||
AddCachingHeaders(responseHeaders, cacheKeyString, cacheDuration);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -487,9 +487,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path);
|
||||
}
|
||||
|
||||
var cacheKey = path + options.DateLastModified.Value.Ticks;
|
||||
|
||||
options.CacheKey = cacheKey.GetMD5();
|
||||
options.ContentFactory = () => Task.FromResult(GetFileStream(path, fileShare));
|
||||
|
||||
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -520,7 +517,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
return GetStaticResult(requestContext, new StaticResultOptions
|
||||
{
|
||||
CacheDuration = cacheDuration,
|
||||
CacheKey = cacheKey,
|
||||
ContentFactory = factoryFn,
|
||||
ContentType = contentType,
|
||||
DateLastModified = lastDateModified,
|
||||
@@ -534,14 +530,10 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var contentType = options.ContentType;
|
||||
var etag = requestContext.Headers.Get("If-None-Match");
|
||||
var cacheKey = etag != null ? new Guid(etag.Trim('\"')) : Guid.Empty;
|
||||
if (!cacheKey.Equals(Guid.Empty))
|
||||
if (!string.IsNullOrEmpty(requestContext.Headers.Get("If-Modified-Since")))
|
||||
{
|
||||
var key = cacheKey.ToString("N");
|
||||
|
||||
// See if the result is already cached in the browser
|
||||
var result = GetCachedResult(requestContext, options.ResponseHeaders, cacheKey, key, options.DateLastModified, options.CacheDuration, contentType);
|
||||
var result = GetCachedResult(requestContext, options.ResponseHeaders, options);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
@@ -553,6 +545,8 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
var isHeadRequest = options.IsHeadRequest || string.Equals(requestContext.Verb, "HEAD", StringComparison.OrdinalIgnoreCase);
|
||||
var factoryFn = options.ContentFactory;
|
||||
var responseHeaders = options.ResponseHeaders;
|
||||
AddCachingHeaders(responseHeaders, options.CacheDuration, false, options.DateLastModified);
|
||||
AddAgeHeader(responseHeaders, options.DateLastModified);
|
||||
|
||||
var rangeHeader = requestContext.Headers.Get("Range");
|
||||
|
||||
@@ -566,21 +560,10 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
};
|
||||
|
||||
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
|
||||
// Generate an ETag based on identifying information - TODO read contents from filesystem instead?
|
||||
var responseId = $"{hasHeaders.ContentType}{options.Path}{hasHeaders.TotalContentLength}";
|
||||
var hashedId = MD5.Create().ComputeHash(Encoding.Default.GetBytes(responseId));
|
||||
hasHeaders.Headers["ETag"] = new Guid(hashedId).ToString("N");
|
||||
|
||||
return hasHeaders;
|
||||
}
|
||||
|
||||
var stream = await factoryFn().ConfigureAwait(false);
|
||||
// Generate an etag based on stream content
|
||||
var streamHash = MD5.Create().ComputeHash(stream);
|
||||
var newEtag = new Guid(streamHash).ToString("N");
|
||||
|
||||
// reset position so the response can re-use it -- TODO is this ok?
|
||||
stream.Position = 0;
|
||||
|
||||
var totalContentLength = options.ContentLength;
|
||||
if (!totalContentLength.HasValue)
|
||||
@@ -603,7 +586,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
};
|
||||
|
||||
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
|
||||
hasHeaders.Headers["ETag"] = newEtag;
|
||||
return hasHeaders;
|
||||
}
|
||||
else
|
||||
@@ -628,7 +610,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
};
|
||||
|
||||
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
|
||||
hasHeaders.Headers["ETag"] = newEtag;
|
||||
return hasHeaders;
|
||||
}
|
||||
}
|
||||
@@ -641,37 +622,28 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// <summary>
|
||||
/// Adds the caching responseHeaders.
|
||||
/// </summary>
|
||||
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, TimeSpan? cacheDuration)
|
||||
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, TimeSpan? cacheDuration,
|
||||
bool noCache, DateTime? lastModifiedDate)
|
||||
{
|
||||
if (cacheDuration.HasValue)
|
||||
{
|
||||
responseHeaders["Cache-Control"] = "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(cacheKey))
|
||||
{
|
||||
responseHeaders["Cache-Control"] = "public";
|
||||
}
|
||||
else
|
||||
if (noCache)
|
||||
{
|
||||
responseHeaders["Cache-Control"] = "no-cache, no-store, must-revalidate";
|
||||
responseHeaders["pragma"] = "no-cache, no-store, must-revalidate";
|
||||
return;
|
||||
}
|
||||
|
||||
AddExpiresHeader(responseHeaders, cacheKey, cacheDuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the expires header.
|
||||
/// </summary>
|
||||
private static void AddExpiresHeader(IDictionary<string, string> responseHeaders, string cacheKey, TimeSpan? cacheDuration)
|
||||
{
|
||||
if (cacheDuration.HasValue)
|
||||
{
|
||||
responseHeaders["Expires"] = DateTime.UtcNow.Add(cacheDuration.Value).ToString("r");
|
||||
responseHeaders["Cache-Control"] = "public, max-age=" + cacheDuration.Value.TotalSeconds;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(cacheKey))
|
||||
else
|
||||
{
|
||||
responseHeaders["Expires"] = "-1";
|
||||
responseHeaders["Cache-Control"] = "public";
|
||||
}
|
||||
|
||||
if (lastModifiedDate.HasValue)
|
||||
{
|
||||
responseHeaders["Last-Modified"] = lastModifiedDate.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -687,32 +659,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
responseHeaders["Age"] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Determines whether [is not modified] [the specified cache key].
|
||||
/// </summary>
|
||||
/// <param name="requestContext">The request context.</param>
|
||||
/// <param name="cacheKey">The cache key.</param>
|
||||
/// <param name="lastDateModified">The last date modified.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
/// <returns><c>true</c> if [is not modified] [the specified cache key]; otherwise, <c>false</c>.</returns>
|
||||
private bool IsNotModified(IRequest requestContext, Guid cacheKey)
|
||||
{
|
||||
var ifNoneMatchHeader = requestContext.Headers.Get("If-None-Match");
|
||||
|
||||
bool hasCacheKey = !cacheKey.Equals(Guid.Empty);
|
||||
|
||||
// Validate If-None-Match
|
||||
if (hasCacheKey && !string.IsNullOrEmpty(ifNoneMatchHeader))
|
||||
{
|
||||
if (Guid.TryParse(ifNoneMatchHeader, out var ifNoneMatch)
|
||||
&& cacheKey.Equals(ifNoneMatch))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether [is not modified] [the specified if modified since].
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -9,7 +10,6 @@ using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.System;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using MediaBrowser.Model.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.IO
|
||||
@@ -22,8 +22,7 @@ namespace Emby.Server.Implementations.IO
|
||||
private IServerConfigurationManager ConfigurationManager { get; set; }
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly List<string> _affectedPaths = new List<string>();
|
||||
private ITimer _timer;
|
||||
private readonly ITimerFactory _timerFactory;
|
||||
private Timer _timer;
|
||||
private readonly object _timerLock = new object();
|
||||
public string Path { get; private set; }
|
||||
|
||||
@@ -31,7 +30,7 @@ namespace Emby.Server.Implementations.IO
|
||||
private readonly IEnvironmentInfo _environmentInfo;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo, ILibraryManager libraryManager1)
|
||||
public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, IEnvironmentInfo environmentInfo, ILibraryManager libraryManager1)
|
||||
{
|
||||
logger.LogDebug("New file refresher created for {0}", path);
|
||||
Path = path;
|
||||
@@ -41,7 +40,6 @@ namespace Emby.Server.Implementations.IO
|
||||
LibraryManager = libraryManager;
|
||||
TaskManager = taskManager;
|
||||
Logger = logger;
|
||||
_timerFactory = timerFactory;
|
||||
_environmentInfo = environmentInfo;
|
||||
_libraryManager = libraryManager1;
|
||||
AddPath(path);
|
||||
@@ -90,7 +88,7 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
if (_timer == null)
|
||||
{
|
||||
_timer = _timerFactory.Create(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
|
||||
_timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -146,8 +144,8 @@ namespace Emby.Server.Implementations.IO
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.Select(GetAffectedBaseItem)
|
||||
.Where(item => item != null)
|
||||
.DistinctBy(i => i.Id)
|
||||
.ToList();
|
||||
.GroupBy(x => x.Id)
|
||||
.Select(x => x.First());
|
||||
|
||||
foreach (var item in itemsToRefresh)
|
||||
{
|
||||
@@ -189,13 +187,13 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
item = LibraryManager.FindByPath(path, null);
|
||||
|
||||
path = _fileSystem.GetDirectoryName(path);
|
||||
path = System.IO.Path.GetDirectoryName(path);
|
||||
}
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
// If the item has been deleted find the first valid parent that still exists
|
||||
while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path))
|
||||
while (!Directory.Exists(item.Path) && !File.Exists(item.Path))
|
||||
{
|
||||
item = item.GetOwner() ?? item.GetParent();
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.System;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using MediaBrowser.Model.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.IO
|
||||
@@ -35,7 +34,7 @@ namespace Emby.Server.Implementations.IO
|
||||
/// <summary>
|
||||
/// Any file name ending in any of these will be ignored by the watchers
|
||||
/// </summary>
|
||||
private readonly string[] _alwaysIgnoreFiles = new string[]
|
||||
private readonly HashSet<string> _alwaysIgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"small.jpg",
|
||||
"albumart.jpg",
|
||||
@@ -54,7 +53,7 @@ namespace Emby.Server.Implementations.IO
|
||||
".actors"
|
||||
};
|
||||
|
||||
private readonly string[] _alwaysIgnoreExtensions = new string[]
|
||||
private readonly HashSet<string> _alwaysIgnoreExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
// thumbs.db
|
||||
".db",
|
||||
@@ -134,13 +133,18 @@ namespace Emby.Server.Implementations.IO
|
||||
private IServerConfigurationManager ConfigurationManager { get; set; }
|
||||
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ITimerFactory _timerFactory;
|
||||
private readonly IEnvironmentInfo _environmentInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LibraryMonitor" /> class.
|
||||
/// </summary>
|
||||
public LibraryMonitor(ILoggerFactory loggerFactory, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ITimerFactory timerFactory, ISystemEvents systemEvents, IEnvironmentInfo environmentInfo)
|
||||
public LibraryMonitor(
|
||||
ILoggerFactory loggerFactory,
|
||||
ITaskManager taskManager,
|
||||
ILibraryManager libraryManager,
|
||||
IServerConfigurationManager configurationManager,
|
||||
IFileSystem fileSystem,
|
||||
IEnvironmentInfo environmentInfo)
|
||||
{
|
||||
if (taskManager == null)
|
||||
{
|
||||
@@ -152,28 +156,10 @@ namespace Emby.Server.Implementations.IO
|
||||
Logger = loggerFactory.CreateLogger(GetType().Name);
|
||||
ConfigurationManager = configurationManager;
|
||||
_fileSystem = fileSystem;
|
||||
_timerFactory = timerFactory;
|
||||
_environmentInfo = environmentInfo;
|
||||
|
||||
systemEvents.Resume += _systemEvents_Resume;
|
||||
}
|
||||
|
||||
private void _systemEvents_Resume(object sender, EventArgs e)
|
||||
{
|
||||
Restart();
|
||||
}
|
||||
|
||||
private void Restart()
|
||||
{
|
||||
Stop();
|
||||
|
||||
if (!_disposed)
|
||||
{
|
||||
Start();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsLibraryMonitorEnabaled(BaseItem item)
|
||||
private bool IsLibraryMonitorEnabled(BaseItem item)
|
||||
{
|
||||
if (item is BasePluginFolder)
|
||||
{
|
||||
@@ -200,7 +186,7 @@ namespace Emby.Server.Implementations.IO
|
||||
var paths = LibraryManager
|
||||
.RootFolder
|
||||
.Children
|
||||
.Where(IsLibraryMonitorEnabaled)
|
||||
.Where(IsLibraryMonitorEnabled)
|
||||
.OfType<Folder>()
|
||||
.SelectMany(f => f.PhysicalLocations)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
@@ -223,7 +209,7 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
private void StartWatching(BaseItem item)
|
||||
{
|
||||
if (IsLibraryMonitorEnabaled(item))
|
||||
if (IsLibraryMonitorEnabled(item))
|
||||
{
|
||||
StartWatchingPath(item.Path);
|
||||
}
|
||||
@@ -287,7 +273,7 @@ namespace Emby.Server.Implementations.IO
|
||||
/// <param name="path">The path.</param>
|
||||
private void StartWatchingPath(string path)
|
||||
{
|
||||
if (!_fileSystem.DirectoryExists(path))
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
// Seeing a crash in the mono runtime due to an exception being thrown on a different thread
|
||||
Logger.LogInformation("Skipping realtime monitor for {0} because the path does not exist", path);
|
||||
@@ -470,8 +456,8 @@ namespace Emby.Server.Implementations.IO
|
||||
var filename = Path.GetFileName(path);
|
||||
|
||||
var monitorPath = !string.IsNullOrEmpty(filename) &&
|
||||
!_alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase) &&
|
||||
!_alwaysIgnoreExtensions.Contains(Path.GetExtension(path) ?? string.Empty, StringComparer.OrdinalIgnoreCase) &&
|
||||
!_alwaysIgnoreFiles.Contains(filename) &&
|
||||
!_alwaysIgnoreExtensions.Contains(Path.GetExtension(path)) &&
|
||||
_alwaysIgnoreSubstrings.All(i => path.IndexOf(i, StringComparison.OrdinalIgnoreCase) == -1);
|
||||
|
||||
// Ignore certain files
|
||||
@@ -493,7 +479,7 @@ namespace Emby.Server.Implementations.IO
|
||||
}
|
||||
|
||||
// Go up a level
|
||||
var parent = _fileSystem.GetDirectoryName(i);
|
||||
var parent = Path.GetDirectoryName(i);
|
||||
if (!string.IsNullOrEmpty(parent))
|
||||
{
|
||||
if (_fileSystem.AreEqual(parent, path))
|
||||
@@ -519,7 +505,7 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
private void CreateRefresher(string path)
|
||||
{
|
||||
var parentPath = _fileSystem.GetDirectoryName(path);
|
||||
var parentPath = Path.GetDirectoryName(path);
|
||||
|
||||
lock (_activeRefreshers)
|
||||
{
|
||||
@@ -548,14 +534,14 @@ namespace Emby.Server.Implementations.IO
|
||||
}
|
||||
|
||||
// They are siblings. Rebase the refresher to the parent folder.
|
||||
if (string.Equals(parentPath, _fileSystem.GetDirectoryName(refresher.Path), StringComparison.Ordinal))
|
||||
if (string.Equals(parentPath, Path.GetDirectoryName(refresher.Path), StringComparison.Ordinal))
|
||||
{
|
||||
refresher.ResetPath(parentPath, path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _timerFactory, _environmentInfo, LibraryManager);
|
||||
var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _environmentInfo, LibraryManager);
|
||||
newRefresher.Completed += NewRefresher_Completed;
|
||||
_activeRefreshers.Add(newRefresher);
|
||||
}
|
||||
@@ -611,20 +597,26 @@ namespace Emby.Server.Implementations.IO
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_disposed = true;
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool dispose)
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (dispose)
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -637,9 +629,10 @@ namespace Emby.Server.Implementations.IO
|
||||
_monitor = monitor;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
public Task RunAsync()
|
||||
{
|
||||
_monitor.Start();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.IO
|
||||
_isEnvironmentCaseInsensitive = environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows;
|
||||
}
|
||||
|
||||
public string DefaultDirectory
|
||||
public virtual string DefaultDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
try
|
||||
{
|
||||
if (DirectoryExists(value))
|
||||
if (Directory.Exists(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
@@ -75,7 +75,7 @@ namespace Emby.Server.Implementations.IO
|
||||
}
|
||||
}
|
||||
|
||||
public void AddShortcutHandler(IShortcutHandler handler)
|
||||
public virtual void AddShortcutHandler(IShortcutHandler handler)
|
||||
{
|
||||
_shortcutHandlers.Add(handler);
|
||||
}
|
||||
@@ -94,13 +94,6 @@ namespace Emby.Server.Implementations.IO
|
||||
}
|
||||
}
|
||||
|
||||
public char DirectorySeparatorChar => Path.DirectorySeparatorChar;
|
||||
|
||||
public string GetFullPath(string path)
|
||||
{
|
||||
return Path.GetFullPath(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified filename is shortcut.
|
||||
/// </summary>
|
||||
@@ -142,7 +135,7 @@ namespace Emby.Server.Implementations.IO
|
||||
return null;
|
||||
}
|
||||
|
||||
public string MakeAbsolutePath(string folderPath, string filePath)
|
||||
public virtual string MakeAbsolutePath(string folderPath, string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath)) return filePath;
|
||||
|
||||
@@ -195,7 +188,7 @@ namespace Emby.Server.Implementations.IO
|
||||
/// or
|
||||
/// target
|
||||
/// </exception>
|
||||
public void CreateShortcut(string shortcutPath, string target)
|
||||
public virtual void CreateShortcut(string shortcutPath, string target)
|
||||
{
|
||||
if (string.IsNullOrEmpty(shortcutPath))
|
||||
{
|
||||
@@ -227,7 +220,7 @@ namespace Emby.Server.Implementations.IO
|
||||
/// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
|
||||
/// <remarks>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> object's
|
||||
/// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and all other properties will reflect the properties of the directory.</remarks>
|
||||
public FileSystemMetadata GetFileSystemInfo(string path)
|
||||
public virtual FileSystemMetadata GetFileSystemInfo(string path)
|
||||
{
|
||||
// Take a guess to try and avoid two file system hits, but we'll double-check by calling Exists
|
||||
if (Path.HasExtension(path))
|
||||
@@ -262,7 +255,7 @@ namespace Emby.Server.Implementations.IO
|
||||
/// <remarks><para>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> object's
|
||||
/// <see cref="FileSystemMetadata.IsDirectory"/> property and the <see cref="FileSystemMetadata.Exists"/> property will both be set to false.</para>
|
||||
/// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></remarks>
|
||||
public FileSystemMetadata GetFileInfo(string path)
|
||||
public virtual FileSystemMetadata GetFileInfo(string path)
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
|
||||
@@ -277,7 +270,7 @@ namespace Emby.Server.Implementations.IO
|
||||
/// <remarks><para>If the specified path points to a file, the returned <see cref="FileSystemMetadata"/> object's
|
||||
/// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and the <see cref="FileSystemMetadata.Exists"/> property will be set to false.</para>
|
||||
/// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></remarks>
|
||||
public FileSystemMetadata GetDirectoryInfo(string path)
|
||||
public virtual FileSystemMetadata GetDirectoryInfo(string path)
|
||||
{
|
||||
var fileInfo = new DirectoryInfo(path);
|
||||
|
||||
@@ -339,24 +332,19 @@ namespace Emby.Server.Implementations.IO
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The space char
|
||||
/// </summary>
|
||||
private const char SpaceChar = ' ';
|
||||
|
||||
/// <summary>
|
||||
/// Takes a filename and removes invalid characters
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
/// <exception cref="ArgumentNullException">filename</exception>
|
||||
public string GetValidFilename(string filename)
|
||||
public virtual string GetValidFilename(string filename)
|
||||
{
|
||||
var builder = new StringBuilder(filename);
|
||||
|
||||
foreach (var c in _invalidFileNameChars)
|
||||
{
|
||||
builder = builder.Replace(c, SpaceChar);
|
||||
builder = builder.Replace(c, ' ');
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
@@ -386,17 +374,17 @@ namespace Emby.Server.Implementations.IO
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>DateTime.</returns>
|
||||
public DateTime GetCreationTimeUtc(string path)
|
||||
public virtual DateTime GetCreationTimeUtc(string path)
|
||||
{
|
||||
return GetCreationTimeUtc(GetFileSystemInfo(path));
|
||||
}
|
||||
|
||||
public DateTime GetCreationTimeUtc(FileSystemMetadata info)
|
||||
public virtual DateTime GetCreationTimeUtc(FileSystemMetadata info)
|
||||
{
|
||||
return info.CreationTimeUtc;
|
||||
}
|
||||
|
||||
public DateTime GetLastWriteTimeUtc(FileSystemMetadata info)
|
||||
public virtual DateTime GetLastWriteTimeUtc(FileSystemMetadata info)
|
||||
{
|
||||
return info.LastWriteTimeUtc;
|
||||
}
|
||||
@@ -425,7 +413,7 @@ namespace Emby.Server.Implementations.IO
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>DateTime.</returns>
|
||||
public DateTime GetLastWriteTimeUtc(string path)
|
||||
public virtual DateTime GetLastWriteTimeUtc(string path)
|
||||
{
|
||||
return GetLastWriteTimeUtc(GetFileSystemInfo(path));
|
||||
}
|
||||
@@ -439,7 +427,7 @@ namespace Emby.Server.Implementations.IO
|
||||
/// <param name="share">The share.</param>
|
||||
/// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
|
||||
/// <returns>FileStream.</returns>
|
||||
public Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, bool isAsync = false)
|
||||
public virtual Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, bool isAsync = false)
|
||||
{
|
||||
if (_supportsAsyncFileStreams && isAsync)
|
||||
{
|
||||
@@ -449,7 +437,7 @@ namespace Emby.Server.Implementations.IO
|
||||
return GetFileStream(path, mode, access, share, FileOpenOptions.None);
|
||||
}
|
||||
|
||||
public Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, FileOpenOptions fileOpenOptions)
|
||||
public virtual Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, FileOpenOptions fileOpenOptions)
|
||||
=> new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 4096, GetFileOptions(fileOpenOptions));
|
||||
|
||||
private static FileOptions GetFileOptions(FileOpenOptions mode)
|
||||
@@ -511,7 +499,7 @@ namespace Emby.Server.Implementations.IO
|
||||
}
|
||||
}
|
||||
|
||||
public void SetHidden(string path, bool isHidden)
|
||||
public virtual void SetHidden(string path, bool isHidden)
|
||||
{
|
||||
if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
|
||||
{
|
||||
@@ -535,7 +523,7 @@ namespace Emby.Server.Implementations.IO
|
||||
}
|
||||
}
|
||||
|
||||
public void SetReadOnly(string path, bool isReadOnly)
|
||||
public virtual void SetReadOnly(string path, bool isReadOnly)
|
||||
{
|
||||
if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
|
||||
{
|
||||
@@ -559,7 +547,7 @@ namespace Emby.Server.Implementations.IO
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAttributes(string path, bool isHidden, bool isReadOnly)
|
||||
public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly)
|
||||
{
|
||||
if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
|
||||
{
|
||||
@@ -611,7 +599,7 @@ namespace Emby.Server.Implementations.IO
|
||||
/// </summary>
|
||||
/// <param name="file1">The file1.</param>
|
||||
/// <param name="file2">The file2.</param>
|
||||
public void SwapFiles(string file1, string file2)
|
||||
public virtual void SwapFiles(string file1, string file2)
|
||||
{
|
||||
if (string.IsNullOrEmpty(file1))
|
||||
{
|
||||
@@ -630,18 +618,13 @@ namespace Emby.Server.Implementations.IO
|
||||
SetHidden(file2, false);
|
||||
|
||||
Directory.CreateDirectory(_tempPath);
|
||||
CopyFile(file1, temp1, true);
|
||||
File.Copy(file1, temp1, true);
|
||||
|
||||
CopyFile(file2, file1, true);
|
||||
CopyFile(temp1, file2, true);
|
||||
File.Copy(file2, file1, true);
|
||||
File.Copy(temp1, file2, true);
|
||||
}
|
||||
|
||||
private static char GetDirectorySeparatorChar(string path)
|
||||
{
|
||||
return Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
public bool ContainsSubPath(string parentPath, string path)
|
||||
public virtual bool ContainsSubPath(string parentPath, string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(parentPath))
|
||||
{
|
||||
@@ -653,19 +636,19 @@ namespace Emby.Server.Implementations.IO
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
var separatorChar = GetDirectorySeparatorChar(parentPath);
|
||||
var separatorChar = Path.DirectorySeparatorChar;
|
||||
|
||||
return path.IndexOf(parentPath.TrimEnd(separatorChar) + separatorChar, StringComparison.OrdinalIgnoreCase) != -1;
|
||||
}
|
||||
|
||||
public bool IsRootPath(string path)
|
||||
public virtual bool IsRootPath(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
var parent = GetDirectoryName(path);
|
||||
var parent = Path.GetDirectoryName(path);
|
||||
|
||||
if (!string.IsNullOrEmpty(parent))
|
||||
{
|
||||
@@ -675,12 +658,7 @@ namespace Emby.Server.Implementations.IO
|
||||
return true;
|
||||
}
|
||||
|
||||
public string GetDirectoryName(string path)
|
||||
{
|
||||
return Path.GetDirectoryName(path);
|
||||
}
|
||||
|
||||
public string NormalizePath(string path)
|
||||
public virtual string NormalizePath(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
@@ -692,10 +670,10 @@ namespace Emby.Server.Implementations.IO
|
||||
return path;
|
||||
}
|
||||
|
||||
return path.TrimEnd(GetDirectorySeparatorChar(path));
|
||||
return path.TrimEnd(Path.DirectorySeparatorChar);
|
||||
}
|
||||
|
||||
public bool AreEqual(string path1, string path2)
|
||||
public virtual bool AreEqual(string path1, string path2)
|
||||
{
|
||||
if (path1 == null && path2 == null)
|
||||
{
|
||||
@@ -710,7 +688,7 @@ namespace Emby.Server.Implementations.IO
|
||||
return string.Equals(NormalizePath(path1), NormalizePath(path2), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public string GetFileNameWithoutExtension(FileSystemMetadata info)
|
||||
public virtual string GetFileNameWithoutExtension(FileSystemMetadata info)
|
||||
{
|
||||
if (info.IsDirectory)
|
||||
{
|
||||
@@ -720,12 +698,7 @@ namespace Emby.Server.Implementations.IO
|
||||
return Path.GetFileNameWithoutExtension(info.FullName);
|
||||
}
|
||||
|
||||
public string GetFileNameWithoutExtension(string path)
|
||||
{
|
||||
return Path.GetFileNameWithoutExtension(path);
|
||||
}
|
||||
|
||||
public bool IsPathFile(string path)
|
||||
public virtual bool IsPathFile(string path)
|
||||
{
|
||||
// Cannot use Path.IsPathRooted because it returns false under mono when using windows-based paths, e.g. C:\\
|
||||
|
||||
@@ -740,23 +713,13 @@ namespace Emby.Server.Implementations.IO
|
||||
//return Path.IsPathRooted(path);
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
public virtual void DeleteFile(string path)
|
||||
{
|
||||
SetAttributes(path, false, false);
|
||||
File.Delete(path);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path, bool recursive)
|
||||
{
|
||||
Directory.Delete(path, recursive);
|
||||
}
|
||||
|
||||
public void CreateDirectory(string path)
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public List<FileSystemMetadata> GetDrives()
|
||||
|
||||
public virtual List<FileSystemMetadata> GetDrives()
|
||||
{
|
||||
// Only include drives in the ready state or this method could end up being very slow, waiting for drives to timeout
|
||||
return DriveInfo.GetDrives().Where(d => d.IsReady).Select(d => new FileSystemMetadata
|
||||
@@ -768,19 +731,19 @@ namespace Emby.Server.Implementations.IO
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
|
||||
public virtual IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
|
||||
{
|
||||
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||
|
||||
return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", searchOption));
|
||||
}
|
||||
|
||||
public IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false)
|
||||
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false)
|
||||
{
|
||||
return GetFiles(path, null, false, recursive);
|
||||
}
|
||||
|
||||
public IEnumerable<FileSystemMetadata> GetFiles(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
|
||||
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
|
||||
{
|
||||
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||
|
||||
@@ -809,7 +772,7 @@ namespace Emby.Server.Implementations.IO
|
||||
return ToMetadata(files);
|
||||
}
|
||||
|
||||
public IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
|
||||
public virtual IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
|
||||
{
|
||||
var directoryInfo = new DirectoryInfo(path);
|
||||
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||
@@ -827,89 +790,19 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
return infos.Select(GetFileSystemMetadata);
|
||||
}
|
||||
|
||||
public string[] ReadAllLines(string path)
|
||||
{
|
||||
return File.ReadAllLines(path);
|
||||
}
|
||||
|
||||
public void WriteAllLines(string path, IEnumerable<string> lines)
|
||||
{
|
||||
File.WriteAllLines(path, lines);
|
||||
}
|
||||
|
||||
public Stream OpenRead(string path)
|
||||
{
|
||||
return File.OpenRead(path);
|
||||
}
|
||||
|
||||
public void CopyFile(string source, string target, bool overwrite)
|
||||
{
|
||||
File.Copy(source, target, overwrite);
|
||||
}
|
||||
|
||||
public void MoveFile(string source, string target)
|
||||
{
|
||||
File.Move(source, target);
|
||||
}
|
||||
|
||||
public void MoveDirectory(string source, string target)
|
||||
{
|
||||
Directory.Move(source, target);
|
||||
}
|
||||
|
||||
public bool DirectoryExists(string path)
|
||||
{
|
||||
return Directory.Exists(path);
|
||||
}
|
||||
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
return File.Exists(path);
|
||||
}
|
||||
|
||||
public string ReadAllText(string path)
|
||||
{
|
||||
return File.ReadAllText(path);
|
||||
}
|
||||
|
||||
public byte[] ReadAllBytes(string path)
|
||||
{
|
||||
return File.ReadAllBytes(path);
|
||||
}
|
||||
|
||||
public void WriteAllText(string path, string text, Encoding encoding)
|
||||
{
|
||||
File.WriteAllText(path, text, encoding);
|
||||
}
|
||||
|
||||
public void WriteAllText(string path, string text)
|
||||
{
|
||||
File.WriteAllText(path, text);
|
||||
}
|
||||
|
||||
public void WriteAllBytes(string path, byte[] bytes)
|
||||
{
|
||||
File.WriteAllBytes(path, bytes);
|
||||
}
|
||||
|
||||
public string ReadAllText(string path, Encoding encoding)
|
||||
{
|
||||
return File.ReadAllText(path, encoding);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
|
||||
|
||||
public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
|
||||
{
|
||||
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||
return Directory.EnumerateDirectories(path, "*", searchOption);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFilePaths(string path, bool recursive = false)
|
||||
public virtual IEnumerable<string> GetFilePaths(string path, bool recursive = false)
|
||||
{
|
||||
return GetFilePaths(path, null, false, recursive);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
|
||||
public virtual IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
|
||||
{
|
||||
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||
|
||||
@@ -938,7 +831,7 @@ namespace Emby.Server.Implementations.IO
|
||||
return files;
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
|
||||
public virtual IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
|
||||
{
|
||||
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||
return Directory.EnumerateFileSystemEntries(path, "*", searchOption);
|
||||
@@ -948,7 +841,7 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX)
|
||||
{
|
||||
RunProcess("chmod", "+x \"" + path + "\"", GetDirectoryName(path));
|
||||
RunProcess("chmod", "+x \"" + path + "\"", Path.GetDirectoryName(path));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
if (string.Equals(Path.GetExtension(shortcutPath), ".mblink", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var path = _fileSystem.ReadAllText(shortcutPath);
|
||||
var path = File.ReadAllText(shortcutPath);
|
||||
|
||||
return _fileSystem.NormalizePath(path);
|
||||
}
|
||||
@@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.IO
|
||||
throw new ArgumentNullException(nameof(targetPath));
|
||||
}
|
||||
|
||||
_fileSystem.WriteAllText(shortcutPath, targetPath);
|
||||
File.WriteAllText(shortcutPath, targetPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
Emby.Server.Implementations/IStartupOptions.cs
Normal file
40
Emby.Server.Implementations/IStartupOptions.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace Emby.Server.Implementations
|
||||
{
|
||||
public interface IStartupOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// --ffmpeg
|
||||
/// </summary>
|
||||
string FFmpegPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --ffprobe
|
||||
/// </summary>
|
||||
string FFprobePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --service
|
||||
/// </summary>
|
||||
bool IsService { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --noautorunwebapp
|
||||
/// </summary>
|
||||
bool NoAutoRunWebApp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --package-name
|
||||
/// </summary>
|
||||
string PackageName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --restartpath
|
||||
/// </summary>
|
||||
string RestartPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --restartargs
|
||||
/// </summary>
|
||||
string RestartArgs { get; }
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,7 @@ namespace Emby.Server.Implementations.Images
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var outputPathWithoutExtension = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N"));
|
||||
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPathWithoutExtension));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPathWithoutExtension));
|
||||
string outputPath = CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0);
|
||||
|
||||
if (string.IsNullOrEmpty(outputPath))
|
||||
@@ -165,7 +165,7 @@ namespace Emby.Server.Implementations.Images
|
||||
|
||||
private string CreateCollage(BaseItem primaryItem, List<BaseItem> items, string outputPath, int width, int height)
|
||||
{
|
||||
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPath));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
|
||||
|
||||
var options = new ImageCollageOptions
|
||||
{
|
||||
@@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.Images
|
||||
{
|
||||
return CreateSquareCollage(item, itemsWithImages, outputPath);
|
||||
}
|
||||
if (item is Playlist || item is MusicGenre || item is Genre || item is GameGenre || item is PhotoAlbum)
|
||||
if (item is Playlist || item is MusicGenre || item is Genre || item is PhotoAlbum)
|
||||
{
|
||||
return CreateSquareCollage(item, itemsWithImages, outputPath);
|
||||
}
|
||||
@@ -300,7 +300,7 @@ namespace Emby.Server.Implementations.Images
|
||||
var ext = Path.GetExtension(image);
|
||||
|
||||
var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ext);
|
||||
FileSystem.CopyFile(image, outputPath, true);
|
||||
File.Copy(image, outputPath, true);
|
||||
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -146,7 +147,7 @@ namespace Emby.Server.Implementations.Library
|
||||
if (parent != null)
|
||||
{
|
||||
// Don't resolve these into audio files
|
||||
if (string.Equals(_fileSystem.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) && _libraryManager.IsAudioFile(filename))
|
||||
if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) && _libraryManager.IsAudioFile(filename))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
try
|
||||
{
|
||||
_fileSystem.DeleteDirectory(metadataPath, true);
|
||||
Directory.Delete(metadataPath, true);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
@@ -395,38 +395,33 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
foreach (var fileSystemInfo in item.GetDeletePaths().ToList())
|
||||
{
|
||||
try
|
||||
if (File.Exists(fileSystemInfo.FullName))
|
||||
{
|
||||
_logger.LogDebug("Deleting path {path}", fileSystemInfo.FullName);
|
||||
if (fileSystemInfo.IsDirectory)
|
||||
try
|
||||
{
|
||||
_fileSystem.DeleteDirectory(fileSystemInfo.FullName, true);
|
||||
_logger.LogDebug("Deleting path {path}", fileSystemInfo.FullName);
|
||||
if (fileSystemInfo.IsDirectory)
|
||||
{
|
||||
Directory.Delete(fileSystemInfo.FullName, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Delete(fileSystemInfo.FullName);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (IOException)
|
||||
{
|
||||
_fileSystem.DeleteFile(fileSystemInfo.FullName);
|
||||
if (isRequiredForDelete)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// may have already been deleted manually by user
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
// may have already been deleted manually by user
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
if (isRequiredForDelete)
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
if (isRequiredForDelete)
|
||||
{
|
||||
throw;
|
||||
if (isRequiredForDelete)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,7 +512,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (forceCaseInsensitive || !ConfigurationManager.Configuration.EnableCaseSensitiveItemIds)
|
||||
{
|
||||
key = key.ToLower();
|
||||
key = key.ToLowerInvariant();
|
||||
}
|
||||
|
||||
key = type.FullName + key;
|
||||
@@ -725,7 +720,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
var rootFolderPath = ConfigurationManager.ApplicationPaths.RootFolderPath;
|
||||
|
||||
_fileSystem.CreateDirectory(rootFolderPath);
|
||||
Directory.CreateDirectory(rootFolderPath);
|
||||
|
||||
var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath))).DeepCopy<Folder, AggregateFolder>();
|
||||
|
||||
@@ -739,7 +734,7 @@ namespace Emby.Server.Implementations.Library
|
||||
// Add in the plug-in folders
|
||||
var path = Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "playlists");
|
||||
|
||||
_fileSystem.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
Folder folder = new PlaylistsFolder
|
||||
{
|
||||
@@ -790,7 +785,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||
|
||||
_fileSystem.CreateDirectory(userRootPath);
|
||||
Directory.CreateDirectory(userRootPath);
|
||||
|
||||
var tmpItem = GetItemById(GetNewItemId(userRootPath, typeof(UserRootFolder))) as UserRootFolder;
|
||||
|
||||
@@ -874,11 +869,6 @@ namespace Emby.Server.Implementations.Library
|
||||
return GetItemByNameId<MusicGenre>(MusicGenre.GetPath, name);
|
||||
}
|
||||
|
||||
public Guid GetGameGenreId(string name)
|
||||
{
|
||||
return GetItemByNameId<GameGenre>(GameGenre.GetPath, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Genre
|
||||
/// </summary>
|
||||
@@ -899,16 +889,6 @@ namespace Emby.Server.Implementations.Library
|
||||
return CreateItemByName<MusicGenre>(MusicGenre.GetPath, name, new DtoOptions(true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the game genre.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns>Task{GameGenre}.</returns>
|
||||
public GameGenre GetGameGenre(string name)
|
||||
{
|
||||
return CreateItemByName<GameGenre>(GameGenre.GetPath, name, new DtoOptions(true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Year
|
||||
/// </summary>
|
||||
@@ -1004,7 +984,7 @@ namespace Emby.Server.Implementations.Library
|
||||
public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
// Ensure the location is available.
|
||||
_fileSystem.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath);
|
||||
Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath);
|
||||
|
||||
return new PeopleValidator(this, _logger, ConfigurationManager, _fileSystem).ValidatePeople(cancellationToken, progress);
|
||||
}
|
||||
@@ -1233,7 +1213,7 @@ namespace Emby.Server.Implementations.Library
|
||||
private string GetCollectionType(string path)
|
||||
{
|
||||
return _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false)
|
||||
.Select(i => _fileSystem.GetFileNameWithoutExtension(i))
|
||||
.Select(i => Path.GetFileNameWithoutExtension(i))
|
||||
.FirstOrDefault(i => !string.IsNullOrEmpty(i));
|
||||
}
|
||||
|
||||
@@ -1375,17 +1355,6 @@ namespace Emby.Server.Implementations.Library
|
||||
return ItemRepository.GetGenres(query);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetGameGenres(InternalItemsQuery query)
|
||||
{
|
||||
if (query.User != null)
|
||||
{
|
||||
AddUserToQuery(query, query.User);
|
||||
}
|
||||
|
||||
SetTopParentOrAncestorIds(query);
|
||||
return ItemRepository.GetGameGenres(query);
|
||||
}
|
||||
|
||||
public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query)
|
||||
{
|
||||
if (query.User != null)
|
||||
@@ -2151,7 +2120,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (item == null || !string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_fileSystem.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
item = new UserView
|
||||
{
|
||||
@@ -2196,7 +2165,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
_fileSystem.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
item = new UserView
|
||||
{
|
||||
@@ -2261,7 +2230,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
_fileSystem.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
item = new UserView
|
||||
{
|
||||
@@ -2329,7 +2298,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
_fileSystem.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
item = new UserView
|
||||
{
|
||||
@@ -2868,7 +2837,7 @@ namespace Emby.Server.Implementations.Library
|
||||
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||
|
||||
var virtualFolderPath = Path.Combine(rootFolderPath, name);
|
||||
while (_fileSystem.DirectoryExists(virtualFolderPath))
|
||||
while (Directory.Exists(virtualFolderPath))
|
||||
{
|
||||
name += "1";
|
||||
virtualFolderPath = Path.Combine(rootFolderPath, name);
|
||||
@@ -2877,7 +2846,7 @@ namespace Emby.Server.Implementations.Library
|
||||
var mediaPathInfos = options.PathInfos;
|
||||
if (mediaPathInfos != null)
|
||||
{
|
||||
var invalidpath = mediaPathInfos.FirstOrDefault(i => !_fileSystem.DirectoryExists(i.Path));
|
||||
var invalidpath = mediaPathInfos.FirstOrDefault(i => !Directory.Exists(i.Path));
|
||||
if (invalidpath != null)
|
||||
{
|
||||
throw new ArgumentException("The specified path does not exist: " + invalidpath.Path + ".");
|
||||
@@ -2888,13 +2857,13 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
try
|
||||
{
|
||||
_fileSystem.CreateDirectory(virtualFolderPath);
|
||||
Directory.CreateDirectory(virtualFolderPath);
|
||||
|
||||
if (!string.IsNullOrEmpty(collectionType))
|
||||
{
|
||||
var path = Path.Combine(virtualFolderPath, collectionType + ".collection");
|
||||
|
||||
_fileSystem.WriteAllBytes(path, Array.Empty<byte>());
|
||||
File.WriteAllBytes(path, Array.Empty<byte>());
|
||||
}
|
||||
|
||||
CollectionFolder.SaveLibraryOptions(virtualFolderPath, options);
|
||||
@@ -2940,7 +2909,7 @@ namespace Emby.Server.Implementations.Library
|
||||
// // We can't validate protocol-based paths, so just allow them
|
||||
// if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1)
|
||||
// {
|
||||
// return _fileSystem.DirectoryExists(path);
|
||||
// return Directory.Exists(path);
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -2968,7 +2937,7 @@ namespace Emby.Server.Implementations.Library
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
if (!_fileSystem.DirectoryExists(path))
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
throw new FileNotFoundException("The path does not exist.");
|
||||
}
|
||||
@@ -2981,11 +2950,11 @@ namespace Emby.Server.Implementations.Library
|
||||
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
|
||||
|
||||
var shortcutFilename = _fileSystem.GetFileNameWithoutExtension(path);
|
||||
var shortcutFilename = Path.GetFileNameWithoutExtension(path);
|
||||
|
||||
var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
|
||||
|
||||
while (_fileSystem.FileExists(lnk))
|
||||
while (File.Exists(lnk))
|
||||
{
|
||||
shortcutFilename += "1";
|
||||
lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
|
||||
@@ -3078,7 +3047,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
var path = Path.Combine(rootFolderPath, name);
|
||||
|
||||
if (!_fileSystem.DirectoryExists(path))
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
throw new FileNotFoundException("The media folder does not exist");
|
||||
}
|
||||
@@ -3087,7 +3056,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
try
|
||||
{
|
||||
_fileSystem.DeleteDirectory(path, true);
|
||||
Directory.Delete(path, true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -3150,7 +3119,7 @@ namespace Emby.Server.Implementations.Library
|
||||
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
|
||||
|
||||
if (!_fileSystem.DirectoryExists(virtualFolderPath))
|
||||
if (!Directory.Exists(virtualFolderPath))
|
||||
{
|
||||
throw new FileNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
@@ -36,7 +35,6 @@ namespace Emby.Server.Implementations.Library
|
||||
private IMediaSourceProvider[] _providers;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
private readonly ITimerFactory _timerFactory;
|
||||
private readonly Func<IMediaEncoder> _mediaEncoder;
|
||||
private ILocalizationManager _localizationManager;
|
||||
private IApplicationPaths _appPaths;
|
||||
@@ -51,7 +49,6 @@ namespace Emby.Server.Implementations.Library
|
||||
IJsonSerializer jsonSerializer,
|
||||
IFileSystem fileSystem,
|
||||
IUserDataManager userDataManager,
|
||||
ITimerFactory timerFactory,
|
||||
Func<IMediaEncoder> mediaEncoder)
|
||||
{
|
||||
_itemRepo = itemRepo;
|
||||
@@ -61,7 +58,6 @@ namespace Emby.Server.Implementations.Library
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_fileSystem = fileSystem;
|
||||
_userDataManager = userDataManager;
|
||||
_timerFactory = timerFactory;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_localizationManager = localizationManager;
|
||||
_appPaths = applicationPaths;
|
||||
@@ -322,18 +318,18 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
private string[] NormalizeLanguage(string language)
|
||||
{
|
||||
if (language != null)
|
||||
if (language == null)
|
||||
{
|
||||
var culture = _localizationManager.FindLanguageInfo(language);
|
||||
if (culture != null)
|
||||
{
|
||||
return culture.ThreeLetterISOLanguageNames;
|
||||
}
|
||||
|
||||
return new string[] { language };
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
return Array.Empty<string>();
|
||||
var culture = _localizationManager.FindLanguageInfo(language);
|
||||
if (culture != null)
|
||||
{
|
||||
return culture.ThreeLetterISOLanguageNames;
|
||||
}
|
||||
|
||||
return new string[] { language };
|
||||
}
|
||||
|
||||
private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
|
||||
@@ -670,7 +666,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (cacheFilePath != null)
|
||||
{
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
|
||||
_jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath);
|
||||
|
||||
//_logger.LogDebug("Saved media info to {0}", cacheFilePath);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
@@ -43,7 +44,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
var filename = Path.GetFileNameWithoutExtension(args.Path);
|
||||
|
||||
// Make sure the image doesn't belong to a video file
|
||||
var files = args.DirectoryService.GetFiles(_fileSystem.GetDirectoryName(args.Path));
|
||||
var files = args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path));
|
||||
var libraryOptions = args.GetLibraryOptions();
|
||||
|
||||
foreach (var file in files)
|
||||
@@ -85,7 +86,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
return false;
|
||||
}
|
||||
|
||||
private static readonly string[] IgnoreFiles =
|
||||
private static readonly HashSet<string> IgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"folder",
|
||||
"thumb",
|
||||
@@ -102,7 +103,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
var filename = Path.GetFileNameWithoutExtension(path) ?? string.Empty;
|
||||
|
||||
if (IgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase))
|
||||
if (IgnoreFiles.Contains(filename))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -112,7 +113,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
return false;
|
||||
}
|
||||
|
||||
return imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.'), StringComparer.OrdinalIgnoreCase);
|
||||
return imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -99,14 +99,12 @@ namespace Emby.Server.Implementations.Library
|
||||
if (!query.IncludeMedia)
|
||||
{
|
||||
AddIfMissing(includeItemTypes, typeof(Genre).Name);
|
||||
AddIfMissing(includeItemTypes, typeof(GameGenre).Name);
|
||||
AddIfMissing(includeItemTypes, typeof(MusicGenre).Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddIfMissing(excludeItemTypes, typeof(Genre).Name);
|
||||
AddIfMissing(excludeItemTypes, typeof(GameGenre).Name);
|
||||
AddIfMissing(excludeItemTypes, typeof(MusicGenre).Name);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Connect;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@@ -211,11 +210,8 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
foreach (var user in users)
|
||||
{
|
||||
if (!user.ConnectLinkType.HasValue || user.ConnectLinkType.Value == UserLinkType.LinkedUser)
|
||||
{
|
||||
user.Policy.IsAdministrator = true;
|
||||
UpdateUserPolicy(user, user.Policy, false);
|
||||
}
|
||||
user.Policy.IsAdministrator = true;
|
||||
UpdateUserPolicy(user, user.Policy, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,13 +269,9 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
// Authenticate using local credentials if not a guest
|
||||
if (!user.ConnectLinkType.HasValue || user.ConnectLinkType.Value != UserLinkType.Guest)
|
||||
{
|
||||
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false);
|
||||
authenticationProvider = authResult.Item1;
|
||||
success = authResult.Item2;
|
||||
}
|
||||
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false);
|
||||
authenticationProvider = authResult.Item1;
|
||||
success = authResult.Item2;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -456,30 +448,30 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
private void UpdateInvalidLoginAttemptCount(User user, int newValue)
|
||||
{
|
||||
if (user.Policy.InvalidLoginAttemptCount != newValue || newValue > 0)
|
||||
if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0)
|
||||
{
|
||||
user.Policy.InvalidLoginAttemptCount = newValue;
|
||||
return;
|
||||
}
|
||||
|
||||
var maxCount = user.Policy.IsAdministrator ? 3 : 5;
|
||||
user.Policy.InvalidLoginAttemptCount = newValue;
|
||||
|
||||
// TODO: Fix
|
||||
/*
|
||||
var fireLockout = false;
|
||||
var maxCount = user.Policy.IsAdministrator ? 3 : 5;
|
||||
|
||||
if (newValue >= maxCount)
|
||||
{
|
||||
_logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture));
|
||||
user.Policy.IsDisabled = true;
|
||||
var fireLockout = false;
|
||||
|
||||
fireLockout = true;
|
||||
}*/
|
||||
if (newValue >= maxCount)
|
||||
{
|
||||
_logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue);
|
||||
user.Policy.IsDisabled = true;
|
||||
|
||||
UpdateUserPolicy(user, user.Policy, false);
|
||||
fireLockout = true;
|
||||
}
|
||||
|
||||
/* if (fireLockout)
|
||||
{
|
||||
UserLockedOut?.Invoke(this, new GenericEventArgs<User>(user));
|
||||
}*/
|
||||
UpdateUserPolicy(user, user.Policy, false);
|
||||
|
||||
if (fireLockout)
|
||||
{
|
||||
UserLockedOut?.Invoke(this, new GenericEventArgs<User>(user));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,9 +546,6 @@ namespace Emby.Server.Implementations.Library
|
||||
LastActivityDate = user.LastActivityDate,
|
||||
LastLoginDate = user.LastLoginDate,
|
||||
Configuration = user.Configuration,
|
||||
ConnectLinkType = user.ConnectLinkType,
|
||||
ConnectUserId = user.ConnectUserId,
|
||||
ConnectUserName = user.ConnectUserName,
|
||||
ServerId = _appHost.SystemId,
|
||||
Policy = user.Policy
|
||||
};
|
||||
@@ -815,11 +804,6 @@ namespace Emby.Server.Implementations.Library
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
if (user.ConnectLinkType.HasValue && user.ConnectLinkType.Value == UserLinkType.Guest)
|
||||
{
|
||||
throw new ArgumentException("Passwords for guests cannot be changed.");
|
||||
}
|
||||
|
||||
await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false);
|
||||
|
||||
UpdateUser(user);
|
||||
@@ -904,7 +888,7 @@ namespace Emby.Server.Implementations.Library
|
||||
// Tuesday, 22 August 2006 06:30 AM
|
||||
text.AppendLine("The pin code will expire at " + localExpirationTime.ToString("f1", CultureInfo.CurrentCulture));
|
||||
|
||||
_fileSystem.WriteAllText(path, text.ToString(), Encoding.UTF8);
|
||||
File.WriteAllText(path, text.ToString(), Encoding.UTF8);
|
||||
|
||||
var result = new PasswordPinCreationResult
|
||||
{
|
||||
@@ -926,11 +910,6 @@ namespace Emby.Server.Implementations.Library
|
||||
null :
|
||||
GetUserByName(enteredUsername);
|
||||
|
||||
if (user != null && user.ConnectLinkType.HasValue && user.ConnectLinkType.Value == UserLinkType.Guest)
|
||||
{
|
||||
throw new ArgumentException("Unable to process forgot password request for guests.");
|
||||
}
|
||||
|
||||
var action = ForgotPasswordAction.InNetworkRequired;
|
||||
string pinFile = null;
|
||||
DateTime? expirationDate = null;
|
||||
@@ -975,10 +954,7 @@ namespace Emby.Server.Implementations.Library
|
||||
_lastPin = null;
|
||||
_lastPasswordPinCreationResult = null;
|
||||
|
||||
var users = Users.Where(i => !i.ConnectLinkType.HasValue || i.ConnectLinkType.Value != UserLinkType.Guest)
|
||||
.ToList();
|
||||
|
||||
foreach (var user in users)
|
||||
foreach (var user in Users)
|
||||
{
|
||||
await ResetPassword(user).ConfigureAwait(false);
|
||||
|
||||
@@ -1029,6 +1005,11 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
var path = GetPolicyFilePath(user);
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return GetDefaultPolicy(user);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
lock (_policySyncLock)
|
||||
@@ -1036,10 +1017,6 @@ namespace Emby.Server.Implementations.Library
|
||||
return (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), path);
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return GetDefaultPolicy(user);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return GetDefaultPolicy(user);
|
||||
@@ -1079,7 +1056,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
var path = GetPolicyFilePath(user);
|
||||
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
lock (_policySyncLock)
|
||||
{
|
||||
@@ -1128,6 +1105,11 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
var path = GetConfigurationFilePath(user);
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return new UserConfiguration();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
lock (_configSyncLock)
|
||||
@@ -1135,10 +1117,6 @@ namespace Emby.Server.Implementations.Library
|
||||
return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path);
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return new UserConfiguration();
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return new UserConfiguration();
|
||||
@@ -1174,7 +1152,7 @@ namespace Emby.Server.Implementations.Library
|
||||
config = _jsonSerializer.DeserializeFromString<UserConfiguration>(json);
|
||||
}
|
||||
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
lock (_configSyncLock)
|
||||
{
|
||||
@@ -1204,9 +1182,11 @@ namespace Emby.Server.Implementations.Library
|
||||
_sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
public Task RunAsync()
|
||||
{
|
||||
_userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
||||
|
||||
@@ -308,9 +308,6 @@ namespace Emby.Server.Implementations.Library
|
||||
mediaTypes.Add(MediaType.Book);
|
||||
mediaTypes.Add(MediaType.Audio);
|
||||
break;
|
||||
case CollectionType.Games:
|
||||
mediaTypes.Add(MediaType.Game);
|
||||
break;
|
||||
case CollectionType.Music:
|
||||
mediaTypes.Add(MediaType.Audio);
|
||||
break;
|
||||
@@ -336,7 +333,6 @@ namespace Emby.Server.Implementations.Library
|
||||
typeof(Person).Name,
|
||||
typeof(Studio).Name,
|
||||
typeof(Year).Name,
|
||||
typeof(GameGenre).Name,
|
||||
typeof(MusicGenre).Name,
|
||||
typeof(Genre).Name
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Validators
|
||||
{
|
||||
/// <summary>
|
||||
/// Class GameGenresPostScanTask
|
||||
/// </summary>
|
||||
public class GameGenresPostScanTask : ILibraryPostScanTask
|
||||
{
|
||||
/// <summary>
|
||||
/// The _library manager
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameGenresPostScanTask" /> class.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public GameGenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
_itemRepo = itemRepo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the specified progress.
|
||||
/// </summary>
|
||||
/// <param name="progress">The progress.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
return new GameGenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Validators
|
||||
{
|
||||
class GameGenresValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// The _library manager
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// The _logger
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
public GameGenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
_itemRepo = itemRepo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the specified progress.
|
||||
/// </summary>
|
||||
/// <param name="progress">The progress.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var names = _itemRepo.GetGameGenreNames();
|
||||
|
||||
var numComplete = 0;
|
||||
var count = names.Count;
|
||||
|
||||
foreach (var name in names)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = _libraryManager.GetGameGenre(name);
|
||||
|
||||
await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Don't clutter the log
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error refreshing {GenreName}", name);
|
||||
}
|
||||
|
||||
numComplete++;
|
||||
double percent = numComplete;
|
||||
percent /= count;
|
||||
percent *= 100;
|
||||
|
||||
progress.Report(percent);
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
@@ -41,7 +42,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||
{
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(targetFile));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
||||
|
||||
using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
||||
{
|
||||
@@ -77,7 +78,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
_logger.LogInformation("Opened recording stream from tuner provider");
|
||||
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(targetFile));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
||||
|
||||
using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
||||
{
|
||||
|
||||
@@ -35,8 +35,6 @@ using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Reflection;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.System;
|
||||
using MediaBrowser.Model.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
@@ -60,13 +58,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
private readonly IProviderManager _providerManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IProcessFactory _processFactory;
|
||||
private readonly ISystemEvents _systemEvents;
|
||||
private readonly IAssemblyInfo _assemblyInfo;
|
||||
private IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
public static EmbyTV Current;
|
||||
|
||||
public event EventHandler DataSourceChanged;
|
||||
public event EventHandler<GenericEventArgs<TimerInfo>> TimerCreated;
|
||||
public event EventHandler<GenericEventArgs<string>> TimerCancelled;
|
||||
|
||||
@@ -89,9 +85,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
ILibraryMonitor libraryMonitor,
|
||||
IProviderManager providerManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
ITimerFactory timerFactory,
|
||||
IProcessFactory processFactory,
|
||||
ISystemEvents systemEvents)
|
||||
IProcessFactory processFactory)
|
||||
{
|
||||
Current = this;
|
||||
|
||||
@@ -105,7 +99,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
_providerManager = providerManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_processFactory = processFactory;
|
||||
_systemEvents = systemEvents;
|
||||
_liveTvManager = (LiveTvManager)liveTvManager;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_assemblyInfo = assemblyInfo;
|
||||
@@ -113,7 +106,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
_streamHelper = streamHelper;
|
||||
|
||||
_seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
|
||||
_timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger, timerFactory);
|
||||
_timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger);
|
||||
_timerProvider.TimerFired += _timerProvider_TimerFired;
|
||||
|
||||
_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
|
||||
@@ -127,19 +120,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
}
|
||||
}
|
||||
|
||||
public async void Start()
|
||||
public async Task Start()
|
||||
{
|
||||
_timerProvider.RestartTimers();
|
||||
|
||||
_systemEvents.Resume += _systemEvents_Resume;
|
||||
await CreateRecordingFolders().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void _systemEvents_Resume(object sender, EventArgs e)
|
||||
{
|
||||
_timerProvider.RestartTimers();
|
||||
}
|
||||
|
||||
private async void OnRecordingFoldersChanged()
|
||||
{
|
||||
await CreateRecordingFolders().ConfigureAwait(false);
|
||||
@@ -284,7 +271,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
foreach (var timer in seriesTimers)
|
||||
{
|
||||
await UpdateTimersForSeriesTimer(timer, false, true).ConfigureAwait(false);
|
||||
UpdateTimersForSeriesTimer(timer, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -772,12 +759,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
_timerProvider.AddOrUpdate(timer, false);
|
||||
}
|
||||
|
||||
await UpdateTimersForSeriesTimer(info, true, false).ConfigureAwait(false);
|
||||
UpdateTimersForSeriesTimer(info, true, false);
|
||||
|
||||
return info.Id;
|
||||
}
|
||||
|
||||
public async Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
|
||||
public Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var instance = _seriesTimerProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
@@ -801,8 +788,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
_seriesTimerProvider.Update(instance);
|
||||
|
||||
await UpdateTimersForSeriesTimer(instance, true, true).ConfigureAwait(false);
|
||||
UpdateTimersForSeriesTimer(instance, true, true);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task UpdateTimerAsync(TimerInfo updatedTimer, CancellationToken cancellationToken)
|
||||
@@ -1436,7 +1425,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
timer.RetryCount++;
|
||||
_timerProvider.AddOrUpdate(timer);
|
||||
}
|
||||
else if (_fileSystem.FileExists(recordPath))
|
||||
else if (File.Exists(recordPath))
|
||||
{
|
||||
timer.RecordingPath = recordPath;
|
||||
timer.Status = RecordingStatus.Completed;
|
||||
@@ -1498,7 +1487,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
_logger.LogInformation("Triggering refresh on {path}", path);
|
||||
|
||||
var item = GetAffectedBaseItem(_fileSystem.GetDirectoryName(path));
|
||||
var item = GetAffectedBaseItem(Path.GetDirectoryName(path));
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
@@ -1509,8 +1498,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
RefreshPaths = new string[]
|
||||
{
|
||||
path,
|
||||
_fileSystem.GetDirectoryName(path),
|
||||
_fileSystem.GetDirectoryName(_fileSystem.GetDirectoryName(path))
|
||||
Path.GetDirectoryName(path),
|
||||
Path.GetDirectoryName(Path.GetDirectoryName(path))
|
||||
}
|
||||
|
||||
}, RefreshPriority.High);
|
||||
@@ -1521,13 +1510,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
BaseItem item = null;
|
||||
|
||||
var parentPath = _fileSystem.GetDirectoryName(path);
|
||||
var parentPath = Path.GetDirectoryName(path);
|
||||
|
||||
while (item == null && !string.IsNullOrEmpty(path))
|
||||
{
|
||||
item = _libraryManager.FindByPath(path, null);
|
||||
|
||||
path = _fileSystem.GetDirectoryName(path);
|
||||
path = Path.GetDirectoryName(path);
|
||||
}
|
||||
|
||||
if (item != null)
|
||||
@@ -1582,7 +1571,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
.Where(i => i.Status == RecordingStatus.Completed && !string.IsNullOrWhiteSpace(i.RecordingPath))
|
||||
.Where(i => string.Equals(i.SeriesTimerId, seriesTimerId, StringComparison.OrdinalIgnoreCase))
|
||||
.OrderByDescending(i => i.EndDate)
|
||||
.Where(i => _fileSystem.FileExists(i.RecordingPath))
|
||||
.Where(i => File.Exists(i.RecordingPath))
|
||||
.Skip(seriesTimer.KeepUpTo - 1)
|
||||
.ToList();
|
||||
|
||||
@@ -1604,7 +1593,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
DtoOptions = new DtoOptions(true)
|
||||
|
||||
}))
|
||||
.Where(i => i.IsFileProtocol && _fileSystem.FileExists(i.Path))
|
||||
.Where(i => i.IsFileProtocol && File.Exists(i.Path))
|
||||
.Skip(seriesTimer.KeepUpTo - 1)
|
||||
.ToList();
|
||||
|
||||
@@ -1685,7 +1674,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
while (FileExists(path, timerId))
|
||||
{
|
||||
var parent = _fileSystem.GetDirectoryName(originalPath);
|
||||
var parent = Path.GetDirectoryName(originalPath);
|
||||
var name = Path.GetFileNameWithoutExtension(originalPath);
|
||||
name += " - " + index.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
@@ -1698,7 +1687,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
private bool FileExists(string path, string timerId)
|
||||
{
|
||||
if (_fileSystem.FileExists(path))
|
||||
if (File.Exists(path))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -1831,12 +1820,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
return;
|
||||
}
|
||||
|
||||
var imageSavePath = Path.Combine(_fileSystem.GetDirectoryName(recordingPath), imageSaveFilenameWithoutExtension);
|
||||
var imageSavePath = Path.Combine(Path.GetDirectoryName(recordingPath), imageSaveFilenameWithoutExtension);
|
||||
|
||||
// preserve original image extension
|
||||
imageSavePath = Path.ChangeExtension(imageSavePath, Path.GetExtension(image.Path));
|
||||
|
||||
_fileSystem.CopyFile(image.Path, imageSavePath, true);
|
||||
File.Copy(image.Path, imageSavePath, true);
|
||||
}
|
||||
|
||||
private async Task SaveRecordingImages(string recordingPath, LiveTvProgram program)
|
||||
@@ -1970,7 +1959,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
var nfoPath = Path.Combine(seriesPath, "tvshow.nfo");
|
||||
|
||||
if (_fileSystem.FileExists(nfoPath))
|
||||
if (File.Exists(nfoPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -2032,7 +2021,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
var nfoPath = Path.ChangeExtension(recordingPath, ".nfo");
|
||||
|
||||
if (_fileSystem.FileExists(nfoPath))
|
||||
if (File.Exists(nfoPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -2202,7 +2191,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
if (lockData)
|
||||
{
|
||||
writer.WriteElementString("lockdata", true.ToString().ToLower());
|
||||
writer.WriteElementString("lockdata", true.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
|
||||
}
|
||||
|
||||
if (item.CriticRating.HasValue)
|
||||
@@ -2355,10 +2344,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateTimersForSeriesTimer(SeriesTimerInfo seriesTimer, bool updateTimerSettings, bool deleteInvalidTimers)
|
||||
private void UpdateTimersForSeriesTimer(SeriesTimerInfo seriesTimer, bool updateTimerSettings, bool deleteInvalidTimers)
|
||||
{
|
||||
var allTimers = GetTimersForSeries(seriesTimer)
|
||||
.ToList();
|
||||
var allTimers = GetTimersForSeries(seriesTimer).ToList();
|
||||
|
||||
|
||||
var enabledTimersForSeries = new List<TimerInfo>();
|
||||
@@ -2697,7 +2685,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
var defaultFolder = RecordingPath;
|
||||
var defaultName = "Recordings";
|
||||
|
||||
if (_fileSystem.DirectoryExists(defaultFolder))
|
||||
if (Directory.Exists(defaultFolder))
|
||||
{
|
||||
list.Add(new VirtualFolderInfo
|
||||
{
|
||||
@@ -2707,7 +2695,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
}
|
||||
|
||||
var customPath = GetConfiguration().MovieRecordingPath;
|
||||
if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && _fileSystem.DirectoryExists(customPath))
|
||||
if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && Directory.Exists(customPath))
|
||||
{
|
||||
list.Add(new VirtualFolderInfo
|
||||
{
|
||||
@@ -2718,7 +2706,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
}
|
||||
|
||||
customPath = GetConfiguration().SeriesRecordingPath;
|
||||
if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && _fileSystem.DirectoryExists(customPath))
|
||||
if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && Directory.Exists(customPath))
|
||||
{
|
||||
list.Add(new VirtualFolderInfo
|
||||
{
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||
{
|
||||
_targetPath = targetFile;
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(targetFile));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
||||
|
||||
var process = _processFactory.Create(new ProcessOptions
|
||||
{
|
||||
@@ -105,7 +105,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
_logger.LogInformation(commandLineLogMessage);
|
||||
|
||||
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(logFilePath));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
|
||||
|
||||
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
|
||||
_logFileStream = _fileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
public class EntryPoint : IServerEntryPoint
|
||||
{
|
||||
public void Run()
|
||||
public Task RunAsync()
|
||||
{
|
||||
EmbyTV.Current.Start();
|
||||
return EmbyTV.Current.Start();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
}
|
||||
|
||||
var file = _dataPath + ".json";
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(file));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(file));
|
||||
|
||||
lock (_fileDataLock)
|
||||
{
|
||||
|
||||
@@ -2,29 +2,27 @@ using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
public class TimerManager : ItemDataProvider<TimerInfo>
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, ITimer> _timers = new ConcurrentDictionary<string, ITimer>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly ConcurrentDictionary<string, Timer> _timers = new ConcurrentDictionary<string, Timer>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
|
||||
private readonly ITimerFactory _timerFactory;
|
||||
|
||||
public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1, ITimerFactory timerFactory)
|
||||
public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1)
|
||||
: base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger = logger1;
|
||||
_timerFactory = timerFactory;
|
||||
}
|
||||
|
||||
public void RestartTimers()
|
||||
@@ -125,7 +123,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
private void StartTimer(TimerInfo item, TimeSpan dueTime)
|
||||
{
|
||||
var timer = _timerFactory.Create(TimerCallback, item.Id, dueTime, TimeSpan.Zero);
|
||||
var timer = new Timer(TimerCallback, item.Id, dueTime, TimeSpan.Zero);
|
||||
|
||||
if (_timers.TryAdd(item.Id, timer))
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.LiveTv.Listings
|
||||
namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
public class XmlTvListingsProvider : IListingsProvider
|
||||
{
|
||||
@@ -61,7 +61,7 @@ namespace Jellyfin.Server.Implementations.LiveTv.Listings
|
||||
|
||||
string cacheFilename = DateTime.UtcNow.DayOfYear.ToString(CultureInfo.InvariantCulture) + "-" + DateTime.UtcNow.Hour.ToString(CultureInfo.InvariantCulture) + ".xml";
|
||||
string cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename);
|
||||
if (_fileSystem.FileExists(cacheFile))
|
||||
if (File.Exists(cacheFile))
|
||||
{
|
||||
return UnzipIfNeeded(path, cacheFile);
|
||||
}
|
||||
@@ -83,9 +83,9 @@ namespace Jellyfin.Server.Implementations.LiveTv.Listings
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFile));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
|
||||
|
||||
_fileSystem.CopyFile(tempFile, cacheFile, true);
|
||||
File.Copy(tempFile, cacheFile, true);
|
||||
|
||||
return UnzipIfNeeded(path, cacheFile);
|
||||
}
|
||||
@@ -122,10 +122,10 @@ namespace Jellyfin.Server.Implementations.LiveTv.Listings
|
||||
|
||||
private string ExtractFirstFileFromGz(string file)
|
||||
{
|
||||
using (var stream = _fileSystem.OpenRead(file))
|
||||
using (var stream = File.OpenRead(file))
|
||||
{
|
||||
string tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString());
|
||||
_fileSystem.CreateDirectory(tempFolder);
|
||||
Directory.CreateDirectory(tempFolder);
|
||||
|
||||
_zipClient.ExtractFirstFileFromGz(stream, tempFolder, "data.xml");
|
||||
|
||||
@@ -135,10 +135,10 @@ namespace Jellyfin.Server.Implementations.LiveTv.Listings
|
||||
|
||||
private string ExtractGz(string file)
|
||||
{
|
||||
using (var stream = _fileSystem.OpenRead(file))
|
||||
using (var stream = File.OpenRead(file))
|
||||
{
|
||||
string tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString());
|
||||
_fileSystem.CreateDirectory(tempFolder);
|
||||
Directory.CreateDirectory(tempFolder);
|
||||
|
||||
_zipClient.ExtractAllFromGz(stream, tempFolder, true);
|
||||
|
||||
@@ -255,7 +255,7 @@ namespace Jellyfin.Server.Implementations.LiveTv.Listings
|
||||
public Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings)
|
||||
{
|
||||
// Assume all urls are valid. check files for existence
|
||||
if (!info.Path.StartsWith("http", StringComparison.OrdinalIgnoreCase) && !_fileSystem.FileExists(info.Path))
|
||||
if (!info.Path.StartsWith("http", StringComparison.OrdinalIgnoreCase) && !File.Exists(info.Path))
|
||||
{
|
||||
throw new FileNotFoundException("Could not find the XmlTv file specified:", info.Path);
|
||||
}
|
||||
|
||||
@@ -399,7 +399,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
var name = serviceName + externalId + InternalVersionNumber;
|
||||
|
||||
return _libraryManager.GetNewItemId(name.ToLower(), typeof(LiveTvChannel));
|
||||
return _libraryManager.GetNewItemId(name.ToLowerInvariant(), typeof(LiveTvChannel));
|
||||
}
|
||||
|
||||
private const string ServiceName = "Emby";
|
||||
@@ -407,21 +407,21 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
var name = ServiceName + externalId + InternalVersionNumber;
|
||||
|
||||
return name.ToLower().GetMD5().ToString("N");
|
||||
return name.ToLowerInvariant().GetMD5().ToString("N");
|
||||
}
|
||||
|
||||
public Guid GetInternalSeriesTimerId(string externalId)
|
||||
{
|
||||
var name = ServiceName + externalId + InternalVersionNumber;
|
||||
|
||||
return name.ToLower().GetMD5();
|
||||
return name.ToLowerInvariant().GetMD5();
|
||||
}
|
||||
|
||||
public Guid GetInternalProgramId(string externalId)
|
||||
{
|
||||
var name = ServiceName + externalId + InternalVersionNumber;
|
||||
|
||||
return _libraryManager.GetNewItemId(name.ToLower(), typeof(LiveTvProgram));
|
||||
return _libraryManager.GetNewItemId(name.ToLowerInvariant(), typeof(LiveTvProgram));
|
||||
}
|
||||
|
||||
public async Task<TimerInfo> GetTimerInfo(TimerInfoDto dto, bool isNew, LiveTvManager liveTv, CancellationToken cancellationToken)
|
||||
|
||||
@@ -6,7 +6,6 @@ using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Library;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
@@ -24,7 +23,6 @@ using MediaBrowser.Controller.Sorting;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
@@ -48,7 +46,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ITaskManager _taskManager;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IProviderManager _providerManager;
|
||||
private readonly Func<IChannelManager> _channelManager;
|
||||
|
||||
private readonly IDtoService _dtoService;
|
||||
@@ -56,7 +53,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
private readonly LiveTvDtoService _tvDtoService;
|
||||
|
||||
private ILiveTvService[] _services = new ILiveTvService[] { };
|
||||
private ILiveTvService[] _services = Array.Empty<ILiveTvService>();
|
||||
|
||||
private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>();
|
||||
private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>();
|
||||
@@ -85,7 +82,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
ITaskManager taskManager,
|
||||
ILocalizationManager localization,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IProviderManager providerManager,
|
||||
IFileSystem fileSystem,
|
||||
Func<IChannelManager> channelManager)
|
||||
{
|
||||
@@ -97,7 +93,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
_taskManager = taskManager;
|
||||
_localization = localization;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_providerManager = providerManager;
|
||||
_fileSystem = fileSystem;
|
||||
_dtoService = dtoService;
|
||||
_userDataManager = userDataManager;
|
||||
@@ -132,8 +127,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
foreach (var service in _services)
|
||||
{
|
||||
service.DataSourceChanged += service_DataSourceChanged;
|
||||
|
||||
if (service is EmbyTV.EmbyTV embyTv)
|
||||
{
|
||||
embyTv.TimerCreated += EmbyTv_TimerCreated;
|
||||
@@ -189,14 +182,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
return EmbyTV.EmbyTV.Current.DiscoverTuners(newDevicesOnly, cancellationToken);
|
||||
}
|
||||
|
||||
void service_DataSourceChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
_taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
|
||||
}
|
||||
}
|
||||
|
||||
public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = query.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(query.UserId);
|
||||
@@ -2158,17 +2143,28 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
private bool _isDisposed = false;
|
||||
private bool _disposed = false;
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool dispose)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (dispose)
|
||||
{
|
||||
_isDisposed = true;
|
||||
// TODO: Dispose stuff
|
||||
}
|
||||
|
||||
_services = null;
|
||||
_listingProviders = null;
|
||||
_tunerHosts = null;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private LiveTvServiceInfo[] GetServiceInfos()
|
||||
@@ -2469,7 +2465,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
.Where(i => i != null)
|
||||
.Where(i => i.IsVisibleStandalone(user))
|
||||
.SelectMany(i => _libraryManager.GetCollectionFolders(i))
|
||||
.DistinctBy(i => i.Id)
|
||||
.GroupBy(x => x.Id)
|
||||
.Select(x => x.First())
|
||||
.OrderBy(i => i.SortName)
|
||||
.ToList();
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
try
|
||||
{
|
||||
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(channelCacheFile));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(channelCacheFile));
|
||||
JsonSerializer.SerializeToFile(channels, channelCacheFile);
|
||||
}
|
||||
catch (IOException)
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
var uri = new Uri(mediaSource.Path);
|
||||
var localPort = _networkManager.GetRandomUnusedUdpPort();
|
||||
|
||||
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(TempFilePath));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath));
|
||||
|
||||
Logger.LogInformation("Opening HDHR UDP Live stream from {host}", uri.Host);
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
UserAgent = _appHost.ApplicationUserAgent
|
||||
});
|
||||
}
|
||||
return Task.FromResult(_fileSystem.OpenRead(url));
|
||||
return Task.FromResult((Stream)File.OpenRead(url));
|
||||
}
|
||||
|
||||
const string ExtInfPrefix = "#EXTINF:";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
@@ -35,7 +36,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
var url = mediaSource.Path;
|
||||
|
||||
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(TempFilePath));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath));
|
||||
|
||||
var typeName = GetType().Name;
|
||||
Logger.LogInformation("Opening " + typeName + " Live stream from {0}", url);
|
||||
@@ -93,7 +94,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
|
||||
var _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
|
||||
|
||||
//OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||
//OpenedMediaSource.Path = tempFile;
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}",
|
||||
"Favorites": "المفضلات",
|
||||
"Folders": "المجلدات",
|
||||
"Games": "الألعاب",
|
||||
"Genres": "أنواع الأفلام",
|
||||
"HeaderAlbumArtists": "فنانو الألبومات",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
@@ -51,8 +50,6 @@
|
||||
"NotificationOptionAudioPlayback": "بدأ تشغيل المقطع الصوتي",
|
||||
"NotificationOptionAudioPlaybackStopped": "تم إيقاف تشغيل المقطع الصوتي",
|
||||
"NotificationOptionCameraImageUploaded": "تم رقع صورة الكاميرا",
|
||||
"NotificationOptionGamePlayback": "تم تشغيل اللعبة",
|
||||
"NotificationOptionGamePlaybackStopped": "تم إيقاف تشغيل اللعبة",
|
||||
"NotificationOptionInstallationFailed": "عملية التنصيب فشلت",
|
||||
"NotificationOptionNewLibraryContent": "تم إضافة محتوى جديد",
|
||||
"NotificationOptionPluginError": "فشل في الملحق",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
||||
"Favorites": "Любими",
|
||||
"Folders": "Папки",
|
||||
"Games": "Игри",
|
||||
"Genres": "Жанрове",
|
||||
"HeaderAlbumArtists": "Изпълнители на албуми",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
@@ -51,8 +50,6 @@
|
||||
"NotificationOptionAudioPlayback": "Възпроизвеждането на звук започна",
|
||||
"NotificationOptionAudioPlaybackStopped": "Възпроизвеждането на звук е спряно",
|
||||
"NotificationOptionCameraImageUploaded": "Изображението от фотоапарата е качено",
|
||||
"NotificationOptionGamePlayback": "Възпроизвеждането на играта започна",
|
||||
"NotificationOptionGamePlaybackStopped": "Възпроизвеждането на играта е спряна",
|
||||
"NotificationOptionInstallationFailed": "Неуспешно инсталиране",
|
||||
"NotificationOptionNewLibraryContent": "Добавено е ново съдържание",
|
||||
"NotificationOptionPluginError": "Грешка в приставка",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"FailedLoginAttemptWithUserName": "Intent de connexió fallit des de {0}",
|
||||
"Favorites": "Preferits",
|
||||
"Folders": "Directoris",
|
||||
"Games": "Jocs",
|
||||
"Genres": "Gèneres",
|
||||
"HeaderAlbumArtists": "Album Artists",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
@@ -51,8 +50,6 @@
|
||||
"NotificationOptionAudioPlayback": "Audio playback started",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||
"NotificationOptionGamePlayback": "Game playback started",
|
||||
"NotificationOptionGamePlaybackStopped": "Game playback stopped",
|
||||
"NotificationOptionInstallationFailed": "Installation failure",
|
||||
"NotificationOptionNewLibraryContent": "New content added",
|
||||
"NotificationOptionPluginError": "Un component ha fallat",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"FailedLoginAttemptWithUserName": "Neúspěšný pokus o přihlášení z {0}",
|
||||
"Favorites": "Oblíbené",
|
||||
"Folders": "Složky",
|
||||
"Games": "Hry",
|
||||
"Genres": "Žánry",
|
||||
"HeaderAlbumArtists": "Umělci alba",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
@@ -51,8 +50,6 @@
|
||||
"NotificationOptionAudioPlayback": "Přehrávání audia zahájeno",
|
||||
"NotificationOptionAudioPlaybackStopped": "Přehrávání audia ukončeno",
|
||||
"NotificationOptionCameraImageUploaded": "Kamerový záznam nahrán",
|
||||
"NotificationOptionGamePlayback": "Spuštění hry zahájeno",
|
||||
"NotificationOptionGamePlaybackStopped": "Hra ukončena",
|
||||
"NotificationOptionInstallationFailed": "Chyba instalace",
|
||||
"NotificationOptionNewLibraryContent": "Přidán nový obsah",
|
||||
"NotificationOptionPluginError": "Chyba zásuvného modulu",
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
"Albums": "Album",
|
||||
"AppDeviceValues": "App: {0}, Enhed: {1}",
|
||||
"Application": "Applikation",
|
||||
"Artists": "Kunstner",
|
||||
"Artists": "Kunstnere",
|
||||
"AuthenticationSucceededWithUserName": "{0} bekræftet med succes",
|
||||
"Books": "Bøger",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"CameraImageUploadedFrom": "Et nyt kamerabillede er blevet uploadet fra {0}",
|
||||
"Channels": "Kanaler",
|
||||
"ChapterNameValue": "Kapitel {0}",
|
||||
"Collections": "Samlinger",
|
||||
@@ -14,45 +14,42 @@
|
||||
"FailedLoginAttemptWithUserName": "Fejlet loginforsøg fra {0}",
|
||||
"Favorites": "Favoritter",
|
||||
"Folders": "Mapper",
|
||||
"Games": "Spil",
|
||||
"Genres": "Genre",
|
||||
"Genres": "Genrer",
|
||||
"HeaderAlbumArtists": "Albumkunstnere",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
"HeaderCameraUploads": "Kamera Uploads",
|
||||
"HeaderContinueWatching": "Fortsæt Afspilning",
|
||||
"HeaderFavoriteAlbums": "Favoritalbum",
|
||||
"HeaderFavoriteArtists": "Favoritkunstnere",
|
||||
"HeaderFavoriteEpisodes": "Favoritepisoder",
|
||||
"HeaderFavoriteShows": "Favorit serier",
|
||||
"HeaderFavoriteSongs": "Favoritsange",
|
||||
"HeaderFavoriteEpisodes": "Favorit-afsnit",
|
||||
"HeaderFavoriteShows": "Favorit-serier",
|
||||
"HeaderFavoriteSongs": "Favorit-sange",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderNextUp": "Næste",
|
||||
"HeaderRecordingGroups": "Optagegrupper",
|
||||
"HeaderRecordingGroups": "Optagelsesgrupper",
|
||||
"HomeVideos": "Hjemmevideoer",
|
||||
"Inherit": "Arv",
|
||||
"Inherit": "Nedarv",
|
||||
"ItemAddedWithName": "{0} blev tilføjet til biblioteket",
|
||||
"ItemRemovedWithName": "{0} blev fjernet fra biblioteket",
|
||||
"LabelIpAddressValue": "IP-adresse: {0}",
|
||||
"LabelRunningTimeValue": "Spilletid: {0}",
|
||||
"Latest": "Seneste",
|
||||
"MessageApplicationUpdated": "Jellyfin Server er blevet opdateret",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server konfigurationssektion {0} er blevet opdateret",
|
||||
"MessageServerConfigurationUpdated": "Serverkonfiguration er blevet opdateret",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server er blevet opdateret til {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurationsafsnit {0} er blevet opdateret",
|
||||
"MessageServerConfigurationUpdated": "Serverkonfigurationen er blevet opdateret",
|
||||
"MixedContent": "Blandet indhold",
|
||||
"Movies": "Film",
|
||||
"Music": "Musik",
|
||||
"MusicVideos": "Musikvideoer",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameInstallFailed": "{0} installationen mislykkedes",
|
||||
"NameSeasonNumber": "Sæson {0}",
|
||||
"NameSeasonUnknown": "Season Unknown",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
"NameSeasonUnknown": "Ukendt Sæson",
|
||||
"NewVersionIsAvailable": "En ny version af Jellyfin Server er tilgængelig til download.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Opdatering til applikation tilgængelig",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Opdatering til applikation installeret",
|
||||
"NotificationOptionAudioPlayback": "Audioafspilning påbegyndt",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audioafspilning stoppet",
|
||||
"NotificationOptionAudioPlayback": "Lydafspilning påbegyndt",
|
||||
"NotificationOptionAudioPlaybackStopped": "Lydafspilning stoppet",
|
||||
"NotificationOptionCameraImageUploaded": "Kamerabillede uploadet",
|
||||
"NotificationOptionGamePlayback": "Afspilning af Spil påbegyndt",
|
||||
"NotificationOptionGamePlaybackStopped": "Afspilning af Spil stoppet",
|
||||
"NotificationOptionInstallationFailed": "Installationsfejl",
|
||||
"NotificationOptionNewLibraryContent": "Nyt indhold tilføjet",
|
||||
"NotificationOptionPluginError": "Pluginfejl",
|
||||
@@ -73,16 +70,16 @@
|
||||
"ProviderValue": "Udbyder: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} fejlet",
|
||||
"ScheduledTaskStartedWithName": "{0} påbegyndt",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"Shows": "Shows",
|
||||
"ServerNameNeedsToBeRestarted": "{0} skal genstartes",
|
||||
"Shows": "Serier",
|
||||
"Songs": "Sange",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server indlæser. Prøv venligst igen om kort tid.",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server er i gang med at starte op. Prøv venligst igen om lidt.",
|
||||
"SubtitleDownloadFailureForItem": "Fejlet i download af undertekster for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitleDownloadFailureFromForItem": "Undertekster kunne ikke downloades fra {0} til {1}",
|
||||
"SubtitlesDownloadedForItem": "Undertekster downloadet for {0}",
|
||||
"Sync": "Synk",
|
||||
"System": "System",
|
||||
"TvShows": "TV Shows",
|
||||
"TvShows": "TV serier",
|
||||
"User": "Bruger",
|
||||
"UserCreatedWithName": "Bruger {0} er blevet oprettet",
|
||||
"UserDeletedWithName": "Brugeren {0} er blevet slettet",
|
||||
@@ -91,10 +88,10 @@
|
||||
"UserOfflineFromDevice": "{0} har afbrudt fra {1}",
|
||||
"UserOnlineFromDevice": "{0} er online fra {1}",
|
||||
"UserPasswordChangedWithName": "Adgangskode er ændret for bruger {0}",
|
||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
||||
"UserPolicyUpdatedWithName": "Brugerpolitik er blevet opdateret for {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} har påbegyndt afspilning af {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1} på {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
}
|
||||
|
||||
@@ -3,64 +3,61 @@
|
||||
"AppDeviceValues": "App: {0}, Gerät: {1}",
|
||||
"Application": "Anwendung",
|
||||
"Artists": "Interpreten",
|
||||
"AuthenticationSucceededWithUserName": "{0} erfolgreich authentifiziert",
|
||||
"AuthenticationSucceededWithUserName": "{0} hat sich angemeldet",
|
||||
"Books": "Bücher",
|
||||
"CameraImageUploadedFrom": "Ein neues Bild wurde hochgeladen von {0}",
|
||||
"CameraImageUploadedFrom": "Ein neues Foto wurde hochgeladen von {0}",
|
||||
"Channels": "Kanäle",
|
||||
"ChapterNameValue": "Kapitel {0}",
|
||||
"Collections": "Sammlungen",
|
||||
"DeviceOfflineWithName": "{0} wurde getrennt",
|
||||
"DeviceOnlineWithName": "{0} ist verbunden",
|
||||
"DeviceOnlineWithName": "{0} hat sich verbunden",
|
||||
"FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
|
||||
"Favorites": "Favoriten",
|
||||
"Folders": "Verzeichnisse",
|
||||
"Games": "Spiele",
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Album-Künstler",
|
||||
"HeaderCameraUploads": "Kamera Uploads",
|
||||
"HeaderAlbumArtists": "Album-Interpreten",
|
||||
"HeaderCameraUploads": "Kamera-Uploads",
|
||||
"HeaderContinueWatching": "Weiterschauen",
|
||||
"HeaderFavoriteAlbums": "Lieblingsalben",
|
||||
"HeaderFavoriteArtists": "Interpreten Favoriten",
|
||||
"HeaderFavoriteArtists": "Lieblings-Interpreten",
|
||||
"HeaderFavoriteEpisodes": "Lieblingsepisoden",
|
||||
"HeaderFavoriteShows": "Lieblingsserien",
|
||||
"HeaderFavoriteSongs": "Lieder Favoriten",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderFavoriteSongs": "Lieblingslieder",
|
||||
"HeaderLiveTV": "Live-TV",
|
||||
"HeaderNextUp": "Als Nächstes",
|
||||
"HeaderRecordingGroups": "Aufnahme-Gruppen",
|
||||
"HomeVideos": "Heimvideos",
|
||||
"Inherit": "Übernehmen",
|
||||
"ItemAddedWithName": "{0} wurde der Bibliothek hinzugefügt",
|
||||
"ItemRemovedWithName": "{0} wurde aus der Bibliothek entfernt",
|
||||
"LabelIpAddressValue": "IP Adresse: {0}",
|
||||
"LabelIpAddressValue": "IP-Adresse: {0}",
|
||||
"LabelRunningTimeValue": "Laufzeit: {0}",
|
||||
"Latest": "Neueste",
|
||||
"MessageApplicationUpdated": "Jellyfin Server wurde auf den neusten Stand gebracht.",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server wurde auf Version {0} aktualisiert",
|
||||
"MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Der Server Einstellungsbereich {0} wurde aktualisiert",
|
||||
"MessageServerConfigurationUpdated": "Server Einstellungen wurden aktualisiert",
|
||||
"MessageServerConfigurationUpdated": "Servereinstellungen wurden aktualisiert",
|
||||
"MixedContent": "Gemischte Inhalte",
|
||||
"Movies": "Filme",
|
||||
"Music": "Musik",
|
||||
"MusicVideos": "Musikvideos",
|
||||
"NameInstallFailed": "{0} Installation fehlgeschlagen",
|
||||
"NameInstallFailed": "Installation von {0} fehlgeschlagen",
|
||||
"NameSeasonNumber": "Staffel {0}",
|
||||
"NameSeasonUnknown": "Staffel unbekannt",
|
||||
"NewVersionIsAvailable": "Eine neue Version von Jellyfin Server steht zum Download bereit.",
|
||||
"NewVersionIsAvailable": "Eine neue Version von Jellyfin-Server steht zum Download bereit.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Anwendungsaktualisierung verfügbar",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Anwendungsaktualisierung installiert",
|
||||
"NotificationOptionAudioPlayback": "Audiowiedergabe gestartet",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audiowiedergabe gestoppt",
|
||||
"NotificationOptionCameraImageUploaded": "Kamera Bild hochgeladen",
|
||||
"NotificationOptionGamePlayback": "Spielwiedergabe gestartet",
|
||||
"NotificationOptionGamePlaybackStopped": "Spielwiedergabe gestoppt",
|
||||
"NotificationOptionCameraImageUploaded": "Foto hochgeladen",
|
||||
"NotificationOptionInstallationFailed": "Installationsfehler",
|
||||
"NotificationOptionNewLibraryContent": "Neuer Inhalt hinzugefügt",
|
||||
"NotificationOptionPluginError": "Plugin Fehler",
|
||||
"NotificationOptionPluginError": "Plugin-Fehler",
|
||||
"NotificationOptionPluginInstalled": "Plugin installiert",
|
||||
"NotificationOptionPluginUninstalled": "Plugin deinstalliert",
|
||||
"NotificationOptionPluginUpdateInstalled": "Pluginaktualisierung installiert",
|
||||
"NotificationOptionServerRestartRequired": "Serverneustart notwendig",
|
||||
"NotificationOptionTaskFailed": "Geplante Aufgaben fehlgeschlagen",
|
||||
"NotificationOptionTaskFailed": "Geplante Aufgabe fehlgeschlagen",
|
||||
"NotificationOptionUserLockedOut": "Benutzer ausgeschlossen",
|
||||
"NotificationOptionVideoPlayback": "Videowiedergabe gestartet",
|
||||
"NotificationOptionVideoPlaybackStopped": "Videowiedergabe gestoppt",
|
||||
@@ -71,18 +68,18 @@
|
||||
"PluginUninstalledWithName": "{0} wurde deinstalliert",
|
||||
"PluginUpdatedWithName": "{0} wurde aktualisiert",
|
||||
"ProviderValue": "Anbieter: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} fehlgeschlagen",
|
||||
"ScheduledTaskStartedWithName": "{0} gestartet",
|
||||
"ScheduledTaskFailedWithName": "{0} ist fehlgeschlagen",
|
||||
"ScheduledTaskStartedWithName": "{0} wurde gestartet",
|
||||
"ServerNameNeedsToBeRestarted": "{0} muss neu gestartet werden",
|
||||
"Shows": "Serien",
|
||||
"Songs": "Songs",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server startet, bitte versuche es gleich noch einmal.",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin-Server startet, bitte versuche es gleich noch einmal.",
|
||||
"SubtitleDownloadFailureForItem": "Download der Untertitel fehlgeschlagen für {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitleDownloadFailureFromForItem": "Untertitel von {0} für {1} konnten nicht heruntergeladen werden",
|
||||
"SubtitlesDownloadedForItem": "Untertitel heruntergeladen für {0}",
|
||||
"Sync": "Synchronisation",
|
||||
"System": "System",
|
||||
"TvShows": "TV Sendungen",
|
||||
"TvShows": "TV-Sendungen",
|
||||
"User": "Benutzer",
|
||||
"UserCreatedWithName": "Benutzer {0} wurde erstellt",
|
||||
"UserDeletedWithName": "Benutzer {0} wurde gelöscht",
|
||||
@@ -91,10 +88,10 @@
|
||||
"UserOfflineFromDevice": "{0} wurde getrennt von {1}",
|
||||
"UserOnlineFromDevice": "{0} ist online von {1}",
|
||||
"UserPasswordChangedWithName": "Das Passwort für Benutzer {0} wurde geändert",
|
||||
"UserPolicyUpdatedWithName": "Benutzerrichtlinie wurde für {0} aktualisiert",
|
||||
"UserStartedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} gestartet",
|
||||
"UserStoppedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} beendet",
|
||||
"ValueHasBeenAddedToLibrary": "{0} wurde ihrer Bibliothek hinzugefügt",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"UserPolicyUpdatedWithName": "Benutzerrichtlinie von {0} wurde aktualisiert",
|
||||
"UserStartedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} gestartet",
|
||||
"UserStoppedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} beendet",
|
||||
"ValueHasBeenAddedToLibrary": "{0} wurde deiner Bibliothek hinzugefügt",
|
||||
"ValueSpecialEpisodeName": "Extra - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"FailedLoginAttemptWithUserName": "Αποτυχημένη προσπάθεια σύνδεσης από {0}",
|
||||
"Favorites": "Αγαπημένα",
|
||||
"Folders": "Φάκελοι",
|
||||
"Games": "Παιχνίδια",
|
||||
"Genres": "Είδη",
|
||||
"HeaderAlbumArtists": "Άλμπουμ Καλλιτεχνών",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
@@ -51,8 +50,6 @@
|
||||
"NotificationOptionAudioPlayback": "Η αναπαραγωγή ήχου ξεκίνησε",
|
||||
"NotificationOptionAudioPlaybackStopped": "Η αναπαραγωγή ήχου σταμάτησε",
|
||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||
"NotificationOptionGamePlayback": "Η αναπαραγωγή του παιχνιδιού ξεκίνησε",
|
||||
"NotificationOptionGamePlaybackStopped": "Η αναπαραγωγή του παιχνιδιού σταμάτησε",
|
||||
"NotificationOptionInstallationFailed": "Αποτυχία εγκατάστασης",
|
||||
"NotificationOptionNewLibraryContent": "Προστέθηκε νέο περιεχόμενο",
|
||||
"NotificationOptionPluginError": "Αποτυχία του plugin",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
||||
"Favorites": "Favourites",
|
||||
"Folders": "Folders",
|
||||
"Games": "Games",
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Album Artists",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
@@ -51,8 +50,6 @@
|
||||
"NotificationOptionAudioPlayback": "Audio playback started",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||
"NotificationOptionGamePlayback": "Game playback started",
|
||||
"NotificationOptionGamePlaybackStopped": "Game playback stopped",
|
||||
"NotificationOptionInstallationFailed": "Installation failure",
|
||||
"NotificationOptionNewLibraryContent": "New content added",
|
||||
"NotificationOptionPluginError": "Plugin failure",
|
||||
@@ -93,7 +90,7 @@
|
||||
"UserPasswordChangedWithName": "Password has been changed for user {0}",
|
||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} has started playing {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
||||
"Favorites": "Favorites",
|
||||
"Folders": "Folders",
|
||||
"Games": "Games",
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Album Artists",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
@@ -31,7 +30,7 @@
|
||||
"Inherit": "Inherit",
|
||||
"ItemAddedWithName": "{0} was added to the library",
|
||||
"ItemRemovedWithName": "{0} was removed from the library",
|
||||
"LabelIpAddressValue": "Ip address: {0}",
|
||||
"LabelIpAddressValue": "IP address: {0}",
|
||||
"LabelRunningTimeValue": "Running time: {0}",
|
||||
"Latest": "Latest",
|
||||
"MessageApplicationUpdated": "Jellyfin Server has been updated",
|
||||
@@ -51,8 +50,6 @@
|
||||
"NotificationOptionAudioPlayback": "Audio playback started",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||
"NotificationOptionGamePlayback": "Game playback started",
|
||||
"NotificationOptionGamePlaybackStopped": "Game playback stopped",
|
||||
"NotificationOptionInstallationFailed": "Installation failure",
|
||||
"NotificationOptionNewLibraryContent": "New content added",
|
||||
"NotificationOptionPluginError": "Plugin failure",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
||||
"Favorites": "Favorites",
|
||||
"Folders": "Folders",
|
||||
"Games": "Games",
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Album Artists",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
@@ -51,8 +50,6 @@
|
||||
"NotificationOptionAudioPlayback": "Audio playback started",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||
"NotificationOptionGamePlayback": "Game playback started",
|
||||
"NotificationOptionGamePlaybackStopped": "Game playback stopped",
|
||||
"NotificationOptionInstallationFailed": "Installation failure",
|
||||
"NotificationOptionNewLibraryContent": "New content added",
|
||||
"NotificationOptionPluginError": "Plugin failure",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión de {0}",
|
||||
"Favorites": "Favoritos",
|
||||
"Folders": "Carpetas",
|
||||
"Games": "Juegos",
|
||||
"Genres": "Géneros",
|
||||
"HeaderAlbumArtists": "Artistas del Álbum",
|
||||
"HeaderCameraUploads": "Subidos desde Camara",
|
||||
@@ -51,8 +50,6 @@
|
||||
"NotificationOptionAudioPlayback": "Reproducción de audio iniciada",
|
||||
"NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida",
|
||||
"NotificationOptionCameraImageUploaded": "Imagen de la cámara subida",
|
||||
"NotificationOptionGamePlayback": "Ejecución de juego iniciada",
|
||||
"NotificationOptionGamePlaybackStopped": "Ejecución de juego detenida",
|
||||
"NotificationOptionInstallationFailed": "Falla de instalación",
|
||||
"NotificationOptionNewLibraryContent": "Nuevo contenido agregado",
|
||||
"NotificationOptionPluginError": "Falla de complemento",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user