Compare commits

..

1 Commits

Author SHA1 Message Date
Vasily 52c399d4d1 Add back taglib-sharp submodule
This fixes v10.1.0 build
2019-01-25 23:46:25 +03:00
471 changed files with 7967 additions and 5226 deletions
-8
View File
@@ -1,8 +0,0 @@
srpm:
dnf -y install git
git submodule update --init --recursive
cd deployment/fedora-package-x64; \
./create_tarball.sh; \
rpmbuild -bs pkg-src/jellyfin.spec \
--define "_sourcedir $$PWD/pkg-src/" \
--define "_srcrpmdir $(outdir)"
+6 -6
View File
@@ -8,23 +8,23 @@ assignees: ''
--- ---
**Describe the bug** **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** **To Reproduce**
<!-- Steps to reproduce the behavior: --> Steps to reproduce the behavior:
1. Go to '...' 1. Go to '...'
2. Click on '....' 2. Click on '....'
3. Scroll down to '....' 3. Scroll down to '....'
4. See error 4. See error
**Expected behavior** **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** **Logs**
<!-- Please paste any log errors. --> Please paste any log errors.
**Screenshots** **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):** **System (please complete the following information):**
- OS: [e.g. Docker, Debian, Windows] - OS: [e.g. Docker, Debian, Windows]
@@ -32,4 +32,4 @@ assignees: ''
- Jellyfin Version: [e.g. 10.0.1] - Jellyfin Version: [e.g. 10.0.1]
**Additional context** **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.** **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** **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** **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** **Additional context**
<!-- Add any other context or screenshots about the feature request here. --> Add any other context or screenshots about the feature request here.
+2 -2
View File
@@ -8,7 +8,7 @@ assignees: ''
--- ---
**Describe the feature you'd like** **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** **Additional context**
<!-- Add any other context or screenshots about the feature request here. --> Add any other context or screenshots about the feature request here.
+3 -5
View File
@@ -1,11 +1,9 @@
<!--
Ensure your title is short, descriptive, and in the imperative mood (Fix X, Change Y, instead of Fixed X, Changed Y). Ensure your title is short, descriptive, and in the imperative mood (Fix X, Change Y, instead of Fixed X, Changed Y).
For a good inspiration of what to write in commit messages and PRs please review https://chris.beams.io/posts/git-commit/ and our https://jellyfin.readthedocs.io/en/latest/developer-docs/contributing/ page. For a good inspiration of what to write in commit messages and PRs please review https://chris.beams.io/posts/git-commit/ and our https://jellyfin.readthedocs.io/en/latest/developer-docs/contributing/ page.
-->
**Changes** **Changes**
<!-- Describe your changes here in 1-5 sentences. --> Describe your changes here in 1-5 sentences.
**Issues** **Issues**
<!-- Tag any issues that this PR solves here. Tag any issues that this PR solves here.
ex. Fixes # --> Fixes #
+3 -1
View File
@@ -1,4 +1,6 @@
[submodule "ThirdParty/taglib-sharp"]
path = ThirdParty/taglib-sharp
url = https://github.com/mono/taglib-sharp
[submodule "MediaBrowser.WebDashboard/jellyfin-web"] [submodule "MediaBrowser.WebDashboard/jellyfin-web"]
path = MediaBrowser.WebDashboard/jellyfin-web path = MediaBrowser.WebDashboard/jellyfin-web
url = https://github.com/jellyfin/jellyfin-web.git url = https://github.com/jellyfin/jellyfin-web.git
branch = .
+3 -3
View File
@@ -92,7 +92,7 @@ namespace BDInfo
} }
DirectoryRoot = DirectoryRoot =
_fileSystem.GetDirectoryInfo(Path.GetDirectoryName(DirectoryBDMV.FullName)); _fileSystem.GetDirectoryInfo(_fileSystem.GetDirectoryName(DirectoryBDMV.FullName));
DirectoryBDJO = DirectoryBDJO =
GetDirectory("BDJO", DirectoryBDMV, 0); GetDirectory("BDJO", DirectoryBDMV, 0);
DirectoryCLIPINF = DirectoryCLIPINF =
@@ -150,7 +150,7 @@ namespace BDInfo
Is3D = true; Is3D = true;
} }
if (File.Exists(Path.Combine(DirectoryRoot.FullName, "FilmIndex.xml"))) if (_fileSystem.FileExists(Path.Combine(DirectoryRoot.FullName, "FilmIndex.xml")))
{ {
IsDBOX = true; IsDBOX = true;
} }
@@ -345,7 +345,7 @@ namespace BDInfo
{ {
return dir; return dir;
} }
var parentFolder = Path.GetDirectoryName(dir.FullName); var parentFolder = _fileSystem.GetDirectoryName(dir.FullName);
if (string.IsNullOrEmpty(parentFolder)) if (string.IsNullOrEmpty(parentFolder))
{ {
dir = null; dir = null;
+2 -2
View File
@@ -1,4 +1,4 @@
//============================================================================ //============================================================================
// BDInfo - Blu-ray Video and Audio Analysis Tool // BDInfo - Blu-ray Video and Audio Analysis Tool
// Copyright © 2010 Cinema Squid // Copyright © 2010 Cinema Squid
// //
@@ -231,7 +231,7 @@ namespace BDInfo
Streams.Clear(); Streams.Clear();
StreamClips.Clear(); StreamClips.Clear();
fileStream = File.OpenRead(FileInfo.FullName); fileStream = _fileSystem.OpenRead(FileInfo.FullName);
fileReader = new BinaryReader(fileStream); fileReader = new BinaryReader(fileStream);
byte[] data = new byte[fileStream.Length]; byte[] data = new byte[fileStream.Length];
+2 -2
View File
@@ -1,4 +1,4 @@
//============================================================================ //============================================================================
// BDInfo - Blu-ray Video and Audio Analysis Tool // BDInfo - Blu-ray Video and Audio Analysis Tool
// Copyright © 2010 Cinema Squid // Copyright © 2010 Cinema Squid
// //
@@ -57,7 +57,7 @@ namespace BDInfo
#endif #endif
Streams.Clear(); Streams.Clear();
fileStream = File.OpenRead(FileInfo.FullName); fileStream = _fileSystem.OpenRead(FileInfo.FullName);
fileReader = new BinaryReader(fileStream); fileReader = new BinaryReader(fileStream);
byte[] data = new byte[fileStream.Length]; byte[] data = new byte[fileStream.Length];
-4
View File
@@ -15,10 +15,6 @@
- [cvium](https://github.com/cvium) - [cvium](https://github.com/cvium)
- [wtayl0r](https://github.com/wtayl0r) - [wtayl0r](https://github.com/wtayl0r)
- [TtheCreator](https://github.com/Tthecreator) - [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 # Emby Contributors
+24 -16
View File
@@ -1,29 +1,37 @@
ARG DOTNET_VERSION=2 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 FROM microsoft/dotnet:${DOTNET_VERSION}-sdk as builder
WORKDIR /repo WORKDIR /repo
COPY . . COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \
RUN bash -c "source deployment/common.build.sh && \ && dotnet clean \
build_jellyfin Jellyfin.Server Release linux-x64 /jellyfin" && dotnet publish \
--configuration release \
--output /jellyfin \
Jellyfin.Server
FROM jellyfin/ffmpeg as ffmpeg
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime 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 # libfontconfig1 is required for Skia
RUN apt-get update \ RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y \ && apt-get install --no-install-recommends --no-install-suggests -y \
libfontconfig1 \ libfontconfig1 \
&& apt-get clean autoclean \ && apt-get clean autoclean \
&& apt-get autoremove \ && apt-get autoremove \
&& rm -rf /var/lib/{apt,dpkg,cache,log} \ && rm -rf /var/lib/{apt,dpkg,cache,log}
&& mkdir -p /cache /config /media \ ENTRYPOINT dotnet /jellyfin/jellyfin.dll -programdata /config
&& chmod 777 /cache /config /media
COPY --from=ffmpeg / /
COPY --from=builder /jellyfin /jellyfin
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
--datadir /config \
--cachedir /cache \
--ffmpeg /usr/local/bin/ffmpeg \
--ffprobe /usr/local/bin/ffprobe
+14 -27
View File
@@ -1,37 +1,24 @@
# Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=3.0 ARG DOTNET_VERSION=3.0
FROM multiarch/qemu-user-static:x86_64-arm as qemu FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch-arm32v7 as builder
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 WORKDIR /repo
COPY . . COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 #TODO Remove or update the sed line when we update dotnet version.
# TODO Remove or update the sed line when we update dotnet version. RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \
RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \; && find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \; \
# Discard objs - may cause failures if exists && dotnet clean -maxcpucount:1 \
RUN find . -type d -name obj | xargs -r rm -r && dotnet publish \
# Build -maxcpucount:1 \
RUN bash -c "source deployment/common.build.sh && \ --configuration release \
build_jellyfin Jellyfin.Server Release linux-arm /jellyfin" --output /jellyfin \
Jellyfin.Server
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm32v7 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 \
&& mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media
COPY --from=builder /jellyfin /jellyfin COPY --from=builder /jellyfin /jellyfin
EXPOSE 8096 EXPOSE 8096
VOLUME /cache /config /media RUN apt-get update \
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \ && apt-get install -y ffmpeg
--datadir /config \ VOLUME /config /media
--cachedir /cache \ ENTRYPOINT dotnet /jellyfin/jellyfin.dll -programdata /config
--ffmpeg /usr/bin/ffmpeg \
--ffprobe /usr/bin/ffprobe
+18 -23
View File
@@ -1,38 +1,33 @@
# Requires binfm_misc registration # Requires binfm_misc registration for aarch64
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register # https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=3.0 ARG DOTNET_VERSION=3.0
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
FROM alpine as qemu_extract FROM alpine as qemu_extract
COPY --from=qemu /usr/bin qemu-aarch64-static.tar.gz COPY --from=qemu /usr/bin qemu_user_static.tgz
RUN tar -xzvf qemu-aarch64-static.tar.gz RUN tar -xzvf qemu_user_static.tgz
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch as builder FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch-arm64v8 as builder
COPY --from=qemu_extract qemu-* /usr/bin
WORKDIR /repo WORKDIR /repo
COPY . . COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 #TODO Remove or update the sed line when we update dotnet version.
# TODO Remove or update the sed line when we update dotnet version. RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \
RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \; && find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \; \
# Discard objs - may cause failures if exists && dotnet clean \
RUN find . -type d -name obj | xargs -r rm -r && dotnet publish \
# Build --configuration release \
RUN bash -c "source deployment/common.build.sh && \ --output /jellyfin \
build_jellyfin Jellyfin.Server Release linux-arm64 /jellyfin" Jellyfin.Server
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm64v8 FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm64v8
COPY --from=qemu_extract qemu-aarch64-static /usr/bin COPY --from=qemu_extract qemu-* /usr/bin
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
&& mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media
COPY --from=builder /jellyfin /jellyfin COPY --from=builder /jellyfin /jellyfin
EXPOSE 8096 EXPOSE 8096
VOLUME /cache /config /media RUN apt-get update \
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \ && apt-get install -y ffmpeg
--datadir /config \ VOLUME /config /media
--cachedir /cache \ ENTRYPOINT dotnet /jellyfin/jellyfin.dll -programdata /config
--ffmpeg /usr/bin/ffmpeg \
--ffprobe /usr/bin/ffprobe
+1 -3
View File
@@ -236,9 +236,7 @@ namespace Emby.Dlna.Api
public object Get(GetIcon request) public object Get(GetIcon request)
{ {
var contentType = "image/" + Path.GetExtension(request.Filename) var contentType = "image/" + Path.GetExtension(request.Filename).TrimStart('.').ToLower();
.TrimStart('.')
.ToLowerInvariant();
var cacheLength = TimeSpan.FromDays(365); var cacheLength = TimeSpan.FromDays(365);
var cacheKey = Request.RawUrl.GetMD5(); var cacheKey = Request.RawUrl.GetMD5();
@@ -76,6 +76,7 @@ namespace Emby.Dlna.ContentDirectory
_dlna.GetDefaultProfile(); _dlna.GetDefaultProfile();
var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase)); var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
string accessToken = null;
var user = GetUser(profile); var user = GetUser(profile);
@@ -84,7 +85,7 @@ namespace Emby.Dlna.ContentDirectory
_libraryManager, _libraryManager,
profile, profile,
serverAddress, serverAddress,
null, accessToken,
_imageProcessor, _imageProcessor,
_userDataManager, _userDataManager,
user, user,
+11 -10
View File
@@ -63,7 +63,7 @@ namespace Emby.Dlna.ContentDirectory
_profile = profile; _profile = profile;
_config = config; _config = config;
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, _logger, mediaEncoder); _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, _logger, libraryManager, mediaEncoder);
} }
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams) protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
@@ -454,7 +454,7 @@ namespace Emby.Dlna.ContentDirectory
User = user, User = user,
Recursive = true, Recursive = true,
IsMissing = false, IsMissing = false,
ExcludeItemTypes = new[] { typeof(Book).Name }, ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
IsFolder = isFolder, IsFolder = isFolder,
MediaTypes = mediaTypes.ToArray(), MediaTypes = mediaTypes.ToArray(),
DtoOptions = GetDtoOptions() DtoOptions = GetDtoOptions()
@@ -483,26 +483,27 @@ namespace Emby.Dlna.ContentDirectory
return GetGenreItems(item, Guid.Empty, user, sort, startIndex, limit); 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)
{ {
if (string.Equals(CollectionType.Music, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase)) var collectionFolder = item as IHasCollectionType;
if (collectionFolder != null && string.Equals(CollectionType.Music, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{ {
return GetMusicFolders(item, user, stubType, sort, startIndex, limit); return GetMusicFolders(item, user, stubType, sort, startIndex, limit);
} }
else if (string.Equals(CollectionType.Movies, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase)) if (collectionFolder != null && string.Equals(CollectionType.Movies, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{ {
return GetMovieFolders(item, user, stubType, sort, startIndex, limit); return GetMovieFolders(item, user, stubType, sort, startIndex, limit);
} }
else if (string.Equals(CollectionType.TvShows, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase)) if (collectionFolder != null && string.Equals(CollectionType.TvShows, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{ {
return GetTvFolders(item, user, stubType, sort, startIndex, limit); return GetTvFolders(item, user, stubType, sort, startIndex, limit);
} }
else if (string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
if (collectionFolder != null && string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{ {
return GetFolders(item, user, stubType, sort, startIndex, limit); return GetFolders(item, user, stubType, sort, startIndex, limit);
} }
else if (string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase)) if (collectionFolder != null && string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{ {
return GetLiveTvChannels(item, user, stubType, sort, startIndex, limit); return GetLiveTvChannels(item, user, stubType, sort, startIndex, limit);
} }
@@ -523,7 +524,7 @@ namespace Emby.Dlna.ContentDirectory
Limit = limit, Limit = limit,
StartIndex = startIndex, StartIndex = startIndex,
IsVirtualItem = false, IsVirtualItem = false,
ExcludeItemTypes = new[] { typeof(Book).Name }, ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
IsPlaceHolder = false, IsPlaceHolder = false,
DtoOptions = GetDtoOptions() DtoOptions = GetDtoOptions()
}; };
+119 -67
View File
@@ -43,30 +43,22 @@ namespace Emby.Dlna.Didl
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
public DidlBuilder( public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress, string accessToken, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, ILogger logger, ILibraryManager libraryManager, IMediaEncoder mediaEncoder)
DeviceProfile profile,
User user,
IImageProcessor imageProcessor,
string serverAddress,
string accessToken,
IUserDataManager userDataManager,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
ILogger logger,
IMediaEncoder mediaEncoder)
{ {
_profile = profile; _profile = profile;
_user = user;
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_serverAddress = serverAddress; _serverAddress = serverAddress;
_accessToken = accessToken;
_userDataManager = userDataManager; _userDataManager = userDataManager;
_localization = localization; _localization = localization;
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_logger = logger; _logger = logger;
_libraryManager = libraryManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_accessToken = accessToken;
_user = user;
} }
public static string NormalizeDlnaMediaUrl(string url) public static string NormalizeDlnaMediaUrl(string url)
@@ -125,8 +117,7 @@ namespace Emby.Dlna.Didl
} }
} }
public void WriteItemElement( public void WriteItemElement(DlnaOptions options,
DlnaOptions options,
XmlWriter writer, XmlWriter writer,
BaseItem item, BaseItem item,
User user, User user,
@@ -241,15 +232,12 @@ namespace Emby.Dlna.Didl
AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo); AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo);
} }
var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken); var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken)
.Where(subtitle => subtitle.DeliveryMethod == SubtitleDeliveryMethod.External)
.ToList();
foreach (var subtitle in subtitleProfiles) foreach (var subtitle in subtitleProfiles)
{ {
if (subtitle.DeliveryMethod != SubtitleDeliveryMethod.External)
{
continue;
}
var subtitleAdded = AddSubtitleElement(writer, subtitle); var subtitleAdded = AddSubtitleElement(writer, subtitle);
if (subtitleAdded && _profile.EnableSingleSubtitleLimit) if (subtitleAdded && _profile.EnableSingleSubtitleLimit)
@@ -262,8 +250,7 @@ namespace Emby.Dlna.Didl
private bool AddSubtitleElement(XmlWriter writer, SubtitleStreamInfo info) private bool AddSubtitleElement(XmlWriter writer, SubtitleStreamInfo info)
{ {
var subtitleProfile = _profile.SubtitleProfiles var subtitleProfile = _profile.SubtitleProfiles
.FirstOrDefault(i => string.Equals(info.Format, i.Format, StringComparison.OrdinalIgnoreCase) .FirstOrDefault(i => string.Equals(info.Format, i.Format, StringComparison.OrdinalIgnoreCase) && i.Method == SubtitleDeliveryMethod.External);
&& i.Method == SubtitleDeliveryMethod.External);
if (subtitleProfile == null) if (subtitleProfile == null)
{ {
@@ -278,7 +265,7 @@ namespace Emby.Dlna.Didl
// <sec:CaptionInfo sec:type="srt">http://192.168.1.3:9999/video.srt</sec:CaptionInfo> // <sec:CaptionInfo sec:type="srt">http://192.168.1.3:9999/video.srt</sec:CaptionInfo>
writer.WriteStartElement("sec", "CaptionInfoEx", null); writer.WriteStartElement("sec", "CaptionInfoEx", null);
writer.WriteAttributeString("sec", "type", null, info.Format.ToLowerInvariant()); writer.WriteAttributeString("sec", "type", null, info.Format.ToLower());
writer.WriteString(info.Url); writer.WriteString(info.Url);
writer.WriteFullEndElement(); writer.WriteFullEndElement();
@@ -295,7 +282,7 @@ namespace Emby.Dlna.Didl
else else
{ {
writer.WriteStartElement(string.Empty, "res", NS_DIDL); writer.WriteStartElement(string.Empty, "res", NS_DIDL);
var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLowerInvariant()); var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLower());
writer.WriteAttributeString("protocolInfo", protocolInfo); writer.WriteAttributeString("protocolInfo", protocolInfo);
writer.WriteString(info.Url); writer.WriteString(info.Url);
@@ -400,39 +387,91 @@ namespace Emby.Dlna.Didl
private string GetDisplayName(BaseItem item, StubType? itemStubType, BaseItem context) private string GetDisplayName(BaseItem item, StubType? itemStubType, BaseItem context)
{ {
if (itemStubType.HasValue) if (itemStubType.HasValue && itemStubType.Value == StubType.Latest)
{ {
switch (itemStubType.Value) return _localization.GetLocalizedString("Latest");
{ }
case StubType.Latest: return _localization.GetLocalizedString("Latest"); if (itemStubType.HasValue && itemStubType.Value == StubType.Playlists)
case StubType.Playlists: return _localization.GetLocalizedString("Playlists"); {
case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists"); return _localization.GetLocalizedString("Playlists");
case StubType.Albums: return _localization.GetLocalizedString("Albums"); }
case StubType.Artists: return _localization.GetLocalizedString("Artists"); if (itemStubType.HasValue && itemStubType.Value == StubType.AlbumArtists)
case StubType.Songs: return _localization.GetLocalizedString("Songs"); {
case StubType.Genres: return _localization.GetLocalizedString("Genres"); return _localization.GetLocalizedString("HeaderAlbumArtists");
case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums"); }
case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists"); if (itemStubType.HasValue && itemStubType.Value == StubType.Albums)
case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs"); {
case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching"); return _localization.GetLocalizedString("Albums");
case StubType.Movies: return _localization.GetLocalizedString("Movies"); }
case StubType.Collections: return _localization.GetLocalizedString("Collections"); if (itemStubType.HasValue && itemStubType.Value == StubType.Artists)
case StubType.Favorites: return _localization.GetLocalizedString("Favorites"); {
case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp"); return _localization.GetLocalizedString("Artists");
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows"); }
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes"); if (itemStubType.HasValue && itemStubType.Value == StubType.Songs)
case StubType.Series: return _localization.GetLocalizedString("Shows"); {
default: break; 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");
} }
if (item is Episode episode && context is Season season) var episode = item as Episode;
var season = context as Season;
if (episode != null && season != null)
{ {
// This is a special embedded within a 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)
{ {
return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name); if (season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
{
return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name);
}
} }
if (item.IndexNumber.HasValue) if (item.IndexNumber.HasValue)
@@ -546,8 +585,10 @@ namespace Emby.Dlna.Didl
public static bool IsIdRoot(string id) public static bool IsIdRoot(string id)
{ {
if (string.IsNullOrWhiteSpace(id) if (string.IsNullOrWhiteSpace(id) ||
|| string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
// Samsung sometimes uses 1 as root // Samsung sometimes uses 1 as root
|| string.Equals(id, "1", StringComparison.OrdinalIgnoreCase)) || string.Equals(id, "1", StringComparison.OrdinalIgnoreCase))
{ {
@@ -767,7 +808,7 @@ namespace Emby.Dlna.Didl
{ {
writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre.musicGenre"); writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre.musicGenre");
} }
else if (item is Genre) else if (item is Genre || item is GameGenre)
{ {
writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre"); writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre");
} }
@@ -803,7 +844,7 @@ namespace Emby.Dlna.Didl
// var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase)) // var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
// ?? PersonType.Actor; // ?? PersonType.Actor;
// AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP); // AddValue(writer, "upnp", type.ToLower(), actor.Name, NS_UPNP);
// index++; // index++;
@@ -891,7 +932,13 @@ namespace Emby.Dlna.Didl
private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer) private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
{ {
ImageDownloadInfo imageInfo = GetImageInfo(item); ImageDownloadInfo imageInfo = null;
// Finally, just use the image from the item
if (imageInfo == null)
{
imageInfo = GetImageInfo(item);
}
if (imageInfo == null) if (imageInfo == null)
{ {
@@ -1047,8 +1094,8 @@ namespace Emby.Dlna.Didl
//{ //{
// var size = _imageProcessor.GetImageSize(imageInfo); // var size = _imageProcessor.GetImageSize(imageInfo);
// width = size.Width; // width = Convert.ToInt32(size.Width);
// height = size.Height; // height = Convert.ToInt32(size.Height);
//} //}
//catch //catch
//{ //{
@@ -1071,7 +1118,7 @@ namespace Emby.Dlna.Didl
}; };
} }
private class ImageDownloadInfo class ImageDownloadInfo
{ {
internal Guid ItemId; internal Guid ItemId;
internal string ImageTag; internal string ImageTag;
@@ -1087,7 +1134,7 @@ namespace Emby.Dlna.Didl
internal ItemImageInfo ItemImageInfo; internal ItemImageInfo ItemImageInfo;
} }
private class ImageUrlInfo class ImageUrlInfo
{ {
internal string Url; internal string Url;
@@ -1106,7 +1153,7 @@ namespace Emby.Dlna.Didl
if (stubType.HasValue) if (stubType.HasValue)
{ {
id = stubType.Value.ToString().ToLowerInvariant() + "_" + id; id = stubType.Value.ToString().ToLower() + "_" + id;
} }
return id; return id;
@@ -1121,7 +1168,8 @@ namespace Emby.Dlna.Didl
info.ImageTag, info.ImageTag,
format, format,
maxWidth.ToString(CultureInfo.InvariantCulture), maxWidth.ToString(CultureInfo.InvariantCulture),
maxHeight.ToString(CultureInfo.InvariantCulture)); maxHeight.ToString(CultureInfo.InvariantCulture)
);
var width = info.Width; var width = info.Width;
var height = info.Height; var height = info.Height;
@@ -1130,11 +1178,15 @@ namespace Emby.Dlna.Didl
if (width.HasValue && height.HasValue) if (width.HasValue && height.HasValue)
{ {
var newSize = DrawingUtils.Resize( var newSize = DrawingUtils.Resize(new ImageSize
new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight); {
Height = height.Value,
Width = width.Value
width = newSize.Width; }, 0, 0, maxWidth, maxHeight);
height = newSize.Height;
width = Convert.ToInt32(newSize.Width);
height = Convert.ToInt32(newSize.Height);
var normalizedFormat = format var normalizedFormat = format
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase); .Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
+9 -10
View File
@@ -4,7 +4,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Emby.Dlna.Profiles; using Emby.Dlna.Profiles;
using Emby.Dlna.Server; using Emby.Dlna.Server;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
@@ -49,11 +48,11 @@ namespace Emby.Dlna
_assemblyInfo = assemblyInfo; _assemblyInfo = assemblyInfo;
} }
public async Task InitProfilesAsync() public void InitProfiles()
{ {
try try
{ {
await ExtractSystemProfilesAsync(); ExtractSystemProfiles();
LoadProfiles(); LoadProfiles();
} }
catch (Exception ex) catch (Exception ex)
@@ -301,7 +300,7 @@ namespace Emby.Dlna
profile = ReserializeProfile(tempProfile); profile = ReserializeProfile(tempProfile);
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N"); profile.Id = path.ToLower().GetMD5().ToString("N");
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile); _profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
@@ -353,14 +352,14 @@ namespace Emby.Dlna
Info = new DeviceProfileInfo Info = new DeviceProfileInfo
{ {
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N"), Id = file.FullName.ToLower().GetMD5().ToString("N"),
Name = _fileSystem.GetFileNameWithoutExtension(file), Name = _fileSystem.GetFileNameWithoutExtension(file),
Type = type Type = type
} }
}; };
} }
private async Task ExtractSystemProfilesAsync() private void ExtractSystemProfiles()
{ {
var namespaceName = GetType().Namespace + ".Profiles.Xml."; var namespaceName = GetType().Namespace + ".Profiles.Xml.";
@@ -380,18 +379,18 @@ namespace Emby.Dlna
if (!fileInfo.Exists || fileInfo.Length != stream.Length) if (!fileInfo.Exists || fileInfo.Length != stream.Length)
{ {
Directory.CreateDirectory(systemProfilesPath); _fileSystem.CreateDirectory(systemProfilesPath);
using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
{ {
await stream.CopyToAsync(fileStream); stream.CopyTo(fileStream);
} }
} }
} }
} }
// Not necessary, but just to make it easy to find // Not necessary, but just to make it easy to find
Directory.CreateDirectory(UserProfilesPath); _fileSystem.CreateDirectory(UserProfilesPath);
} }
public void DeleteProfile(string id) public void DeleteProfile(string id)
@@ -507,7 +506,7 @@ namespace Emby.Dlna
? ImageFormat.Png ? ImageFormat.Png
: ImageFormat.Jpg; : ImageFormat.Jpg;
var resource = GetType().Namespace + ".Images." + filename.ToLowerInvariant(); var resource = GetType().Namespace + ".Images." + filename.ToLower();
return new ImageStream return new ImageStream
{ {
+11 -6
View File
@@ -20,6 +20,7 @@ using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using MediaBrowser.Model.System; using MediaBrowser.Model.System;
using MediaBrowser.Model.Threading;
using MediaBrowser.Model.Xml; using MediaBrowser.Model.Xml;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Rssdp; using Rssdp;
@@ -48,7 +49,8 @@ namespace Emby.Dlna.Main
private readonly IDeviceDiscovery _deviceDiscovery; private readonly IDeviceDiscovery _deviceDiscovery;
private SsdpDevicePublisher _Publisher; private SsdpDevicePublisher _Publisher;
private readonly ITimerFactory _timerFactory;
private readonly ISocketFactory _socketFactory; private readonly ISocketFactory _socketFactory;
private readonly IEnvironmentInfo _environmentInfo; private readonly IEnvironmentInfo _environmentInfo;
private readonly INetworkManager _networkManager; private readonly INetworkManager _networkManager;
@@ -76,6 +78,7 @@ namespace Emby.Dlna.Main
IDeviceDiscovery deviceDiscovery, IDeviceDiscovery deviceDiscovery,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
ISocketFactory socketFactory, ISocketFactory socketFactory,
ITimerFactory timerFactory,
IEnvironmentInfo environmentInfo, IEnvironmentInfo environmentInfo,
INetworkManager networkManager, INetworkManager networkManager,
IUserViewManager userViewManager, IUserViewManager userViewManager,
@@ -96,6 +99,7 @@ namespace Emby.Dlna.Main
_deviceDiscovery = deviceDiscovery; _deviceDiscovery = deviceDiscovery;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_socketFactory = socketFactory; _socketFactory = socketFactory;
_timerFactory = timerFactory;
_environmentInfo = environmentInfo; _environmentInfo = environmentInfo;
_networkManager = networkManager; _networkManager = networkManager;
_logger = loggerFactory.CreateLogger("Dlna"); _logger = loggerFactory.CreateLogger("Dlna");
@@ -121,9 +125,9 @@ namespace Emby.Dlna.Main
Current = this; Current = this;
} }
public async Task RunAsync() public void Run()
{ {
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false); ((DlnaManager)_dlnaManager).InitProfiles();
ReloadComponents(); ReloadComponents();
@@ -229,7 +233,7 @@ namespace Emby.Dlna.Main
try try
{ {
_Publisher = new SsdpDevicePublisher(_communicationsServer, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion); _Publisher = new SsdpDevicePublisher(_communicationsServer, _timerFactory, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion);
_Publisher.LogFunction = LogMessage; _Publisher.LogFunction = LogMessage;
_Publisher.SupportPnpRootDevice = false; _Publisher.SupportPnpRootDevice = false;
@@ -259,7 +263,7 @@ namespace Emby.Dlna.Main
var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address); _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address.ToString());
var descriptorUri = "/dlna/" + udn + "/description.xml"; var descriptorUri = "/dlna/" + udn + "/description.xml";
var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorUri); var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorUri);
@@ -349,7 +353,8 @@ namespace Emby.Dlna.Main
_userDataManager, _userDataManager,
_localization, _localization,
_mediaSourceManager, _mediaSourceManager,
_mediaEncoder); _mediaEncoder,
_timerFactory);
_manager.Start(); _manager.Start();
} }
+112 -135
View File
@@ -10,6 +10,7 @@ using Emby.Dlna.Server;
using Emby.Dlna.Ssdp; using Emby.Dlna.Ssdp;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo
@@ -18,7 +19,7 @@ namespace Emby.Dlna.PlayTo
{ {
#region Fields & Properties #region Fields & Properties
private Timer _timer; private ITimer _timer;
public DeviceInfo Properties { get; set; } public DeviceInfo Properties { get; set; }
@@ -39,7 +40,12 @@ namespace Emby.Dlna.PlayTo
public TimeSpan? Duration { get; set; } public TimeSpan? Duration { get; set; }
public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(0); private TimeSpan _position = TimeSpan.FromSeconds(0);
public TimeSpan Position
{
get => _position;
set => _position = value;
}
public TRANSPORTSTATE TransportState { get; private set; } public TRANSPORTSTATE TransportState { get; private set; }
@@ -55,20 +61,24 @@ namespace Emby.Dlna.PlayTo
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
public DateTime DateLastActivity { get; private set; }
public Action OnDeviceUnavailable { get; set; } public Action OnDeviceUnavailable { get; set; }
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config) private readonly ITimerFactory _timerFactory;
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config, ITimerFactory timerFactory)
{ {
Properties = deviceProperties; Properties = deviceProperties;
_httpClient = httpClient; _httpClient = httpClient;
_logger = logger; _logger = logger;
_config = config; _config = config;
_timerFactory = timerFactory;
} }
public void Start() public void Start()
{ {
_logger.LogDebug("Dlna Device.Start"); _logger.LogDebug("Dlna Device.Start");
_timer = new Timer(TimerCallback, null, 1000, Timeout.Infinite); _timer = _timerFactory.Create(TimerCallback, null, 1000, Timeout.Infinite);
} }
private DateTime _lastVolumeRefresh; private DateTime _lastVolumeRefresh;
@@ -109,9 +119,7 @@ namespace Emby.Dlna.PlayTo
lock (_timerLock) lock (_timerLock)
{ {
if (_disposed) if (_disposed)
{
return; return;
}
_volumeRefreshActive = true; _volumeRefreshActive = true;
@@ -128,9 +136,7 @@ namespace Emby.Dlna.PlayTo
lock (_timerLock) lock (_timerLock)
{ {
if (_disposed) if (_disposed)
{
return; return;
}
_volumeRefreshActive = false; _volumeRefreshActive = false;
@@ -138,6 +144,11 @@ namespace Emby.Dlna.PlayTo
} }
} }
public void OnPlaybackStartedExternally()
{
RestartTimer(true);
}
#region Commanding #region Commanding
public Task VolumeDown(CancellationToken cancellationToken) public Task VolumeDown(CancellationToken cancellationToken)
@@ -322,9 +333,7 @@ namespace Emby.Dlna.PlayTo
private string CreateDidlMeta(string value) private string CreateDidlMeta(string value)
{ {
if (string.IsNullOrEmpty(value)) if (string.IsNullOrEmpty(value))
{
return string.Empty; return string.Empty;
}
return DescriptionXmlBuilder.Escape(value); return DescriptionXmlBuilder.Escape(value);
} }
@@ -333,11 +342,10 @@ namespace Emby.Dlna.PlayTo
{ {
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play"); var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play");
if (command == null) if (command == null)
{
return Task.CompletedTask; return Task.CompletedTask;
}
var service = GetAvTransportService(); var service = GetAvTransportService();
if (service == null) if (service == null)
{ {
throw new InvalidOperationException("Unable to find service"); throw new InvalidOperationException("Unable to find service");
@@ -361,9 +369,7 @@ namespace Emby.Dlna.PlayTo
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop"); var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
if (command == null) if (command == null)
{
return; return;
}
var service = GetAvTransportService(); var service = GetAvTransportService();
@@ -379,9 +385,7 @@ namespace Emby.Dlna.PlayTo
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause"); var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
if (command == null) if (command == null)
{
return; return;
}
var service = GetAvTransportService(); var service = GetAvTransportService();
@@ -401,9 +405,7 @@ namespace Emby.Dlna.PlayTo
private async void TimerCallback(object sender) private async void TimerCallback(object sender)
{ {
if (_disposed) if (_disposed)
{
return; return;
}
try try
{ {
@@ -423,6 +425,8 @@ namespace Emby.Dlna.PlayTo
return; return;
} }
DateLastActivity = DateTime.UtcNow;
if (transportState.HasValue) if (transportState.HasValue)
{ {
// If we're not playing anything no need to get additional data // If we're not playing anything no need to get additional data
@@ -501,9 +505,7 @@ namespace Emby.Dlna.PlayTo
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume"); var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
if (command == null) if (command == null)
{
return; return;
}
var service = GetServiceRenderingControl(); var service = GetServiceRenderingControl();
@@ -516,17 +518,13 @@ namespace Emby.Dlna.PlayTo
.ConfigureAwait(false); .ConfigureAwait(false);
if (result == null || result.Document == null) if (result == null || result.Document == null)
{
return; return;
}
var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null); var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null);
var volumeValue = volume?.Value; var volumeValue = volume == null ? null : volume.Value;
if (string.IsNullOrWhiteSpace(volumeValue)) if (string.IsNullOrWhiteSpace(volumeValue))
{
return; return;
}
Volume = int.Parse(volumeValue, UsCulture); Volume = int.Parse(volumeValue, UsCulture);
@@ -547,9 +545,7 @@ namespace Emby.Dlna.PlayTo
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute"); var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
if (command == null) if (command == null)
{
return; return;
}
var service = GetServiceRenderingControl(); var service = GetServiceRenderingControl();
@@ -564,44 +560,39 @@ namespace Emby.Dlna.PlayTo
if (result == null || result.Document == null) if (result == null || result.Document == null)
return; return;
var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse") var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse").Select(i => i.Element("CurrentMute")).FirstOrDefault(i => i != null);
.Select(i => i.Element("CurrentMute")) var value = valueNode == null ? null : valueNode.Value;
.FirstOrDefault(i => i != null);
IsMuted = string.Equals(valueNode?.Value, "1", StringComparison.OrdinalIgnoreCase); IsMuted = string.Equals(value, "1", StringComparison.OrdinalIgnoreCase);
} }
private async Task<TRANSPORTSTATE?> GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken) private async Task<TRANSPORTSTATE?> GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken)
{ {
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo"); var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
if (command == null) if (command == null)
{
return null; return null;
}
var service = GetAvTransportService(); var service = GetAvTransportService();
if (service == null) if (service == null)
{
return null; return null;
}
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType), false) var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType), false)
.ConfigureAwait(false); .ConfigureAwait(false);
if (result == null || result.Document == null) if (result == null || result.Document == null)
{
return null; return null;
}
var transportState = var transportState =
result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null); result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
var transportStateValue = transportState == null ? null : transportState.Value; var transportStateValue = transportState == null ? null : transportState.Value;
if (transportStateValue != null if (transportStateValue != null)
&& Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state))
{ {
return state; if (Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state))
{
return state;
}
} }
return null; return null;
@@ -611,11 +602,10 @@ namespace Emby.Dlna.PlayTo
{ {
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo"); var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo");
if (command == null) if (command == null)
{
return null; return null;
}
var service = GetAvTransportService(); var service = GetAvTransportService();
if (service == null) if (service == null)
{ {
throw new InvalidOperationException("Unable to find service"); throw new InvalidOperationException("Unable to find service");
@@ -627,9 +617,7 @@ namespace Emby.Dlna.PlayTo
.ConfigureAwait(false); .ConfigureAwait(false);
if (result == null || result.Document == null) if (result == null || result.Document == null)
{
return null; return null;
}
var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault(); var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault();
@@ -669,13 +657,11 @@ namespace Emby.Dlna.PlayTo
return null; return null;
} }
private async Task<(bool, uBaseObject)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken) private async Task<Tuple<bool, uBaseObject>> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken)
{ {
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo"); var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
if (command == null) if (command == null)
{ return new Tuple<bool, uBaseObject>(false, null);
return (false, null);
}
var service = GetAvTransportService(); var service = GetAvTransportService();
@@ -690,9 +676,7 @@ namespace Emby.Dlna.PlayTo
.ConfigureAwait(false); .ConfigureAwait(false);
if (result == null || result.Document == null) 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 trackUriElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i != null);
var trackUri = trackUriElem == null ? null : trackUriElem.Value; var trackUri = trackUriElem == null ? null : trackUriElem.Value;
@@ -700,8 +684,8 @@ namespace Emby.Dlna.PlayTo
var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null); var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
var duration = durationElem == null ? null : durationElem.Value; var duration = durationElem == null ? null : durationElem.Value;
if (!string.IsNullOrWhiteSpace(duration) if (!string.IsNullOrWhiteSpace(duration) &&
&& !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
{ {
Duration = TimeSpan.Parse(duration, UsCulture); Duration = TimeSpan.Parse(duration, UsCulture);
} }
@@ -723,14 +707,14 @@ namespace Emby.Dlna.PlayTo
if (track == null) if (track == null)
{ {
//If track is null, some vendors do this, use GetMediaInfo instead //If track is null, some vendors do this, use GetMediaInfo instead
return (true, null); return new Tuple<bool, uBaseObject>(true, null);
} }
var trackString = (string)track; var trackString = (string)track;
if (string.IsNullOrWhiteSpace(trackString) || string.Equals(trackString, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) if (string.IsNullOrWhiteSpace(trackString) || string.Equals(trackString, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
{ {
return (true, null); return new Tuple<bool, uBaseObject>(true, null);
} }
XElement uPnpResponse; XElement uPnpResponse;
@@ -751,7 +735,7 @@ namespace Emby.Dlna.PlayTo
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Unable to parse xml {0}", trackString); _logger.LogError(ex, "Unable to parse xml {0}", trackString);
return (true, null); return new Tuple<bool, uBaseObject>(true, null);
} }
} }
@@ -759,7 +743,7 @@ namespace Emby.Dlna.PlayTo
var uTrack = CreateUBaseObject(e, trackUri); var uTrack = CreateUBaseObject(e, trackUri);
return (true, uTrack); return new Tuple<bool, uBaseObject>(true, uTrack);
} }
private static uBaseObject CreateUBaseObject(XElement container, string trackUri) private static uBaseObject CreateUBaseObject(XElement container, string trackUri)
@@ -817,9 +801,11 @@ namespace Emby.Dlna.PlayTo
private async Task<TransportCommands> GetAVProtocolAsync(CancellationToken cancellationToken) private async Task<TransportCommands> GetAVProtocolAsync(CancellationToken cancellationToken)
{ {
if (AvCommands != null) var avCommands = AvCommands;
if (avCommands != null)
{ {
return AvCommands; return avCommands;
} }
if (_disposed) if (_disposed)
@@ -839,15 +825,18 @@ namespace Emby.Dlna.PlayTo
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
AvCommands = TransportCommands.Create(document); avCommands = TransportCommands.Create(document);
return AvCommands; AvCommands = avCommands;
return avCommands;
} }
private async Task<TransportCommands> GetRenderingProtocolAsync(CancellationToken cancellationToken) private async Task<TransportCommands> GetRenderingProtocolAsync(CancellationToken cancellationToken)
{ {
if (RendererCommands != null) var rendererCommands = RendererCommands;
if (rendererCommands != null)
{ {
return RendererCommands; return rendererCommands;
} }
if (_disposed) if (_disposed)
@@ -856,6 +845,7 @@ namespace Emby.Dlna.PlayTo
} }
var avService = GetServiceRenderingControl(); var avService = GetServiceRenderingControl();
if (avService == null) if (avService == null)
{ {
throw new ArgumentException("Device AvService is null"); throw new ArgumentException("Device AvService is null");
@@ -867,8 +857,9 @@ namespace Emby.Dlna.PlayTo
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync"); _logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
RendererCommands = TransportCommands.Create(document); rendererCommands = TransportCommands.Create(document);
return RendererCommands; RendererCommands = rendererCommands;
return rendererCommands;
} }
private string NormalizeUrl(string baseUrl, string url) private string NormalizeUrl(string baseUrl, string url)
@@ -880,103 +871,85 @@ namespace Emby.Dlna.PlayTo
} }
if (!url.Contains("/")) if (!url.Contains("/"))
{
url = "/dmr/" + url; url = "/dmr/" + url;
}
if (!url.StartsWith("/")) if (!url.StartsWith("/"))
{
url = "/" + url; url = "/" + url;
}
return baseUrl + 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, CancellationToken cancellationToken) public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, ITimerFactory timerFactory, CancellationToken cancellationToken)
{ {
var ssdpHttpClient = new SsdpHttpClient(httpClient, config); var ssdpHttpClient = new SsdpHttpClient(httpClient, config);
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false); var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
var deviceProperties = new DeviceInfo();
var friendlyNames = new List<string>(); var friendlyNames = new List<string>();
var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault(); var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault();
if (name != null && !string.IsNullOrWhiteSpace(name.Value)) if (name != null && !string.IsNullOrWhiteSpace(name.Value))
{
friendlyNames.Add(name.Value); friendlyNames.Add(name.Value);
}
var room = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault(); var room = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault();
if (room != null && !string.IsNullOrWhiteSpace(room.Value)) if (room != null && !string.IsNullOrWhiteSpace(room.Value))
{
friendlyNames.Add(room.Value); friendlyNames.Add(room.Value);
}
var deviceProperties = new DeviceInfo() deviceProperties.Name = string.Join(" ", friendlyNames.ToArray());
{
Name = string.Join(" ", friendlyNames),
BaseUrl = string.Format("http://{0}:{1}", url.Host, url.Port)
};
var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault(); var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault();
if (model != null) if (model != null)
{
deviceProperties.ModelName = model.Value; deviceProperties.ModelName = model.Value;
}
var modelNumber = document.Descendants(uPnpNamespaces.ud.GetName("modelNumber")).FirstOrDefault(); var modelNumber = document.Descendants(uPnpNamespaces.ud.GetName("modelNumber")).FirstOrDefault();
if (modelNumber != null) if (modelNumber != null)
{
deviceProperties.ModelNumber = modelNumber.Value; deviceProperties.ModelNumber = modelNumber.Value;
}
var uuid = document.Descendants(uPnpNamespaces.ud.GetName("UDN")).FirstOrDefault(); var uuid = document.Descendants(uPnpNamespaces.ud.GetName("UDN")).FirstOrDefault();
if (uuid != null) if (uuid != null)
{
deviceProperties.UUID = uuid.Value; deviceProperties.UUID = uuid.Value;
}
var manufacturer = document.Descendants(uPnpNamespaces.ud.GetName("manufacturer")).FirstOrDefault(); var manufacturer = document.Descendants(uPnpNamespaces.ud.GetName("manufacturer")).FirstOrDefault();
if (manufacturer != null) if (manufacturer != null)
{
deviceProperties.Manufacturer = manufacturer.Value; deviceProperties.Manufacturer = manufacturer.Value;
}
var manufacturerUrl = document.Descendants(uPnpNamespaces.ud.GetName("manufacturerURL")).FirstOrDefault(); var manufacturerUrl = document.Descendants(uPnpNamespaces.ud.GetName("manufacturerURL")).FirstOrDefault();
if (manufacturerUrl != null) if (manufacturerUrl != null)
{
deviceProperties.ManufacturerUrl = manufacturerUrl.Value; deviceProperties.ManufacturerUrl = manufacturerUrl.Value;
}
var presentationUrl = document.Descendants(uPnpNamespaces.ud.GetName("presentationURL")).FirstOrDefault(); var presentationUrl = document.Descendants(uPnpNamespaces.ud.GetName("presentationURL")).FirstOrDefault();
if (presentationUrl != null) if (presentationUrl != null)
{
deviceProperties.PresentationUrl = presentationUrl.Value; deviceProperties.PresentationUrl = presentationUrl.Value;
}
var modelUrl = document.Descendants(uPnpNamespaces.ud.GetName("modelURL")).FirstOrDefault(); var modelUrl = document.Descendants(uPnpNamespaces.ud.GetName("modelURL")).FirstOrDefault();
if (modelUrl != null) if (modelUrl != null)
{
deviceProperties.ModelUrl = modelUrl.Value; deviceProperties.ModelUrl = modelUrl.Value;
}
var serialNumber = document.Descendants(uPnpNamespaces.ud.GetName("serialNumber")).FirstOrDefault(); var serialNumber = document.Descendants(uPnpNamespaces.ud.GetName("serialNumber")).FirstOrDefault();
if (serialNumber != null) if (serialNumber != null)
{
deviceProperties.SerialNumber = serialNumber.Value; deviceProperties.SerialNumber = serialNumber.Value;
}
var modelDescription = document.Descendants(uPnpNamespaces.ud.GetName("modelDescription")).FirstOrDefault(); var modelDescription = document.Descendants(uPnpNamespaces.ud.GetName("modelDescription")).FirstOrDefault();
if (modelDescription != null) if (modelDescription != null)
{
deviceProperties.ModelDescription = modelDescription.Value; deviceProperties.ModelDescription = modelDescription.Value;
}
deviceProperties.BaseUrl = string.Format("http://{0}:{1}", url.Host, url.Port);
var icon = document.Descendants(uPnpNamespaces.ud.GetName("icon")).FirstOrDefault(); var icon = document.Descendants(uPnpNamespaces.ud.GetName("icon")).FirstOrDefault();
if (icon != null) if (icon != null)
{ {
deviceProperties.Icon = CreateIcon(icon); deviceProperties.Icon = CreateIcon(icon);
@@ -985,15 +958,12 @@ namespace Emby.Dlna.PlayTo
foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList"))) foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList")))
{ {
if (services == null) if (services == null)
{
continue; continue;
}
var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service")); var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service"));
if (servicesList == null) if (servicesList == null)
{
continue; continue;
}
foreach (var element in servicesList) foreach (var element in servicesList)
{ {
@@ -1006,7 +976,9 @@ namespace Emby.Dlna.PlayTo
} }
} }
return new Device(deviceProperties, httpClient, logger, config); var device = new Device(deviceProperties, httpClient, logger, config, timerFactory);
return device;
} }
#endregion #endregion
@@ -1093,10 +1065,13 @@ namespace Emby.Dlna.PlayTo
private void OnPlaybackStart(uBaseObject mediaInfo) private void OnPlaybackStart(uBaseObject mediaInfo)
{ {
PlaybackStart?.Invoke(this, new PlaybackStartEventArgs if (PlaybackStart != null)
{ {
MediaInfo = mediaInfo PlaybackStart.Invoke(this, new PlaybackStartEventArgs
}); {
MediaInfo = mediaInfo
});
}
} }
private void OnPlaybackProgress(uBaseObject mediaInfo) private void OnPlaybackProgress(uBaseObject mediaInfo)
@@ -1107,56 +1082,58 @@ namespace Emby.Dlna.PlayTo
return; return;
} }
PlaybackProgress?.Invoke(this, new PlaybackProgressEventArgs if (PlaybackProgress != null)
{ {
MediaInfo = mediaInfo PlaybackProgress.Invoke(this, new PlaybackProgressEventArgs
}); {
MediaInfo = mediaInfo
});
}
} }
private void OnPlaybackStop(uBaseObject mediaInfo) private void OnPlaybackStop(uBaseObject mediaInfo)
{ {
if (PlaybackStopped != null)
PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs
{ {
MediaInfo = mediaInfo PlaybackStopped.Invoke(this, new PlaybackStoppedEventArgs
}); {
MediaInfo = mediaInfo
});
}
} }
private void OnMediaChanged(uBaseObject old, uBaseObject newMedia) private void OnMediaChanged(uBaseObject old, uBaseObject newMedia)
{ {
MediaChanged?.Invoke(this, new MediaChangedEventArgs if (MediaChanged != null)
{ {
OldMediaInfo = old, MediaChanged.Invoke(this, new MediaChangedEventArgs
NewMediaInfo = newMedia {
}); OldMediaInfo = old,
NewMediaInfo = newMedia
});
}
} }
#region IDisposable #region IDisposable
bool _disposed; bool _disposed;
public void Dispose() public void Dispose()
{ {
Dispose(true); if (!_disposed)
GC.SuppressFinalize(this); {
_disposed = true;
DisposeTimer();
}
} }
protected virtual void Dispose(bool disposing) private void DisposeTimer()
{ {
if (_disposed) if (_timer != null)
{ {
return; _timer.Dispose();
_timer = null;
} }
if (disposing)
{
_timer?.Dispose();
}
_timer = null;
Properties = null;
_disposed = true;
} }
#endregion #endregion
+11 -21
View File
@@ -42,43 +42,30 @@ namespace Emby.Dlna.PlayTo
private readonly IDeviceDiscovery _deviceDiscovery; private readonly IDeviceDiscovery _deviceDiscovery;
private readonly string _serverAddress; private readonly string _serverAddress;
private readonly string _accessToken; private readonly string _accessToken;
private readonly DateTime _creationTime;
public bool IsSessionActive => !_disposed && _device != null; public bool IsSessionActive => !_disposed && _device != null;
public bool SupportsMediaControl => IsSessionActive; public bool SupportsMediaControl => IsSessionActive;
public PlayToController( 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)
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; _session = session;
_sessionManager = sessionManager; _sessionManager = sessionManager;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_logger = logger;
_dlnaManager = dlnaManager; _dlnaManager = dlnaManager;
_userManager = userManager; _userManager = userManager;
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_serverAddress = serverAddress; _serverAddress = serverAddress;
_accessToken = accessToken;
_deviceDiscovery = deviceDiscovery; _deviceDiscovery = deviceDiscovery;
_userDataManager = userDataManager; _userDataManager = userDataManager;
_localization = localization; _localization = localization;
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_config = config; _config = config;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_accessToken = accessToken;
_logger = logger;
_creationTime = DateTime.UtcNow;
} }
public void Init(Device device) public void Init(Device device)
@@ -387,7 +374,9 @@ namespace Emby.Dlna.PlayTo
return _device.IsPaused ? _device.SetPlay(CancellationToken.None) : _device.SetPause(CancellationToken.None); return _device.IsPaused ? _device.SetPlay(CancellationToken.None) : _device.SetPause(CancellationToken.None);
case PlaystateCommand.Seek: case PlaystateCommand.Seek:
return Seek(command.SeekPositionTicks ?? 0); {
return Seek(command.SeekPositionTicks ?? 0);
}
case PlaystateCommand.NextTrack: case PlaystateCommand.NextTrack:
return SetPlaylistIndex(_currentPlaylistIndex + 1); return SetPlaylistIndex(_currentPlaylistIndex + 1);
@@ -453,7 +442,8 @@ namespace Emby.Dlna.PlayTo
var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ?? var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ??
_dlnaManager.GetDefaultProfile(); _dlnaManager.GetDefaultProfile();
var mediaSources = item is IHasMediaSources var hasMediaSources = item as IHasMediaSources;
var mediaSources = hasMediaSources != null
? (_mediaSourceManager.GetStaticMediaSources(item, true, user)) ? (_mediaSourceManager.GetStaticMediaSources(item, true, user))
: new List<MediaSourceInfo>(); : new List<MediaSourceInfo>();
@@ -462,7 +452,7 @@ namespace Emby.Dlna.PlayTo
playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken)); playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken));
var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _mediaEncoder) var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _libraryManager, _mediaEncoder)
.GetItemDidl(_config.GetDlnaConfiguration(), item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo); .GetItemDidl(_config.GetDlnaConfiguration(), item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
playlistItem.Didl = itemXml; playlistItem.Didl = itemXml;
+17 -10
View File
@@ -16,6 +16,7 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo
@@ -38,12 +39,13 @@ namespace Emby.Dlna.PlayTo
private readonly IDeviceDiscovery _deviceDiscovery; private readonly IDeviceDiscovery _deviceDiscovery;
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly ITimerFactory _timerFactory;
private bool _disposed; private bool _disposed;
private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1); private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); 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) 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)
{ {
_logger = logger; _logger = logger;
_sessionManager = sessionManager; _sessionManager = sessionManager;
@@ -59,6 +61,7 @@ namespace Emby.Dlna.PlayTo
_localization = localization; _localization = localization;
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_timerFactory = timerFactory;
} }
public void Start() public void Start()
@@ -89,6 +92,11 @@ namespace Emby.Dlna.PlayTo
return; return;
} }
if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1))
{
return;
}
var cancellationToken = _disposeCancellationTokenSource.Token; var cancellationToken = _disposeCancellationTokenSource.Token;
await _sessionLock.WaitAsync(cancellationToken).ConfigureAwait(false); await _sessionLock.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -100,11 +108,6 @@ namespace Emby.Dlna.PlayTo
return; return;
} }
if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1))
{
return;
}
await AddDevice(info, location, cancellationToken).ConfigureAwait(false); await AddDevice(info, location, cancellationToken).ConfigureAwait(false);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -159,15 +162,17 @@ namespace Emby.Dlna.PlayTo
uuid = location.GetMD5().ToString("N"); uuid = location.GetMD5().ToString("N");
} }
var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion, uuid, null, uri.OriginalString, null); string deviceName = null;
var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion, uuid, deviceName, uri.OriginalString, null);
var controller = sessionInfo.SessionControllers.OfType<PlayToController>().FirstOrDefault(); var controller = sessionInfo.SessionControllers.OfType<PlayToController>().FirstOrDefault();
if (controller == null) if (controller == null)
{ {
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger, cancellationToken).ConfigureAwait(false); var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger, _timerFactory, cancellationToken).ConfigureAwait(false);
string deviceName = device.Properties.Name; deviceName = device.Properties.Name;
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName); _sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
@@ -181,6 +186,8 @@ namespace Emby.Dlna.PlayTo
serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress); serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress);
} }
string accessToken = null;
controller = new PlayToController(sessionInfo, controller = new PlayToController(sessionInfo,
_sessionManager, _sessionManager,
_libraryManager, _libraryManager,
@@ -189,7 +196,7 @@ namespace Emby.Dlna.PlayTo
_userManager, _userManager,
_imageProcessor, _imageProcessor,
serverAddress, serverAddress,
null, accessToken,
_deviceDiscovery, _deviceDiscovery,
_userDataManager, _userDataManager,
_localization, _localization,
+69 -67
View File
@@ -107,19 +107,19 @@ namespace Emby.Dlna.Server
'&' '&'
}; };
private static readonly string[] s_escapeStringPairs = new[] private static readonly string[] s_escapeStringPairs = new string[]
{ {
"<", "<",
"&lt;", "&lt;",
">", ">",
"&gt;", "&gt;",
"\"", "\"",
"&quot;", "&quot;",
"'", "'",
"&apos;", "&apos;",
"&", "&",
"&amp;" "&amp;"
}; };
private static string GetEscapeSequence(char c) private static string GetEscapeSequence(char c)
{ {
@@ -133,7 +133,7 @@ namespace Emby.Dlna.Server
return result; return result;
} }
} }
return c.ToString(CultureInfo.InvariantCulture); return c.ToString();
} }
/// <summary>Replaces invalid XML characters in a string with their valid XML equivalent.</summary> /// <summary>Replaces invalid XML characters in a string with their valid XML equivalent.</summary>
@@ -145,7 +145,6 @@ namespace Emby.Dlna.Server
{ {
return null; return null;
} }
StringBuilder stringBuilder = null; StringBuilder stringBuilder = null;
int length = str.Length; int length = str.Length;
int num = 0; int num = 0;
@@ -231,9 +230,9 @@ namespace Emby.Dlna.Server
var serverName = new string(characters); var serverName = new string(characters);
var name = _profile.FriendlyName?.Replace("${HostName}", serverName, StringComparison.OrdinalIgnoreCase); var name = (_profile.FriendlyName ?? string.Empty).Replace("${HostName}", serverName, StringComparison.OrdinalIgnoreCase);
return name ?? string.Empty; return name;
} }
private void AppendIconList(StringBuilder builder) private void AppendIconList(StringBuilder builder)
@@ -296,62 +295,65 @@ namespace Emby.Dlna.Server
} }
private IEnumerable<DeviceIcon> GetIcons() private IEnumerable<DeviceIcon> GetIcons()
=> new[] {
var list = new List<DeviceIcon>();
list.Add(new DeviceIcon
{ {
new DeviceIcon MimeType = "image/png",
{ Depth = "24",
MimeType = "image/png", Width = 240,
Depth = "24", Height = 240,
Width = 240, Url = "icons/logo240.png"
Height = 240, });
Url = "icons/logo240.png"
},
new DeviceIcon list.Add(new DeviceIcon
{ {
MimeType = "image/jpeg", MimeType = "image/jpeg",
Depth = "24", Depth = "24",
Width = 240, Width = 240,
Height = 240, Height = 240,
Url = "icons/logo240.jpg" Url = "icons/logo240.jpg"
}, });
new DeviceIcon list.Add(new DeviceIcon
{ {
MimeType = "image/png", MimeType = "image/png",
Depth = "24", Depth = "24",
Width = 120, Width = 120,
Height = 120, Height = 120,
Url = "icons/logo120.png" Url = "icons/logo120.png"
}, });
new DeviceIcon list.Add(new DeviceIcon
{ {
MimeType = "image/jpeg", MimeType = "image/jpeg",
Depth = "24", Depth = "24",
Width = 120, Width = 120,
Height = 120, Height = 120,
Url = "icons/logo120.jpg" Url = "icons/logo120.jpg"
}, });
new DeviceIcon list.Add(new DeviceIcon
{ {
MimeType = "image/png", MimeType = "image/png",
Depth = "24", Depth = "24",
Width = 48, Width = 48,
Height = 48, Height = 48,
Url = "icons/logo48.png" Url = "icons/logo48.png"
}, });
new DeviceIcon list.Add(new DeviceIcon
{ {
MimeType = "image/jpeg", MimeType = "image/jpeg",
Depth = "24", Depth = "24",
Width = 48, Width = 48,
Height = 48, Height = 48,
Url = "icons/logo48.jpg" Url = "icons/logo48.jpg"
} });
};
return list;
}
private IEnumerable<DeviceService> GetServices() private IEnumerable<DeviceService> GetServices()
{ {
+6 -2
View File
@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Rssdp; using Rssdp;
using Rssdp.Infrastructure; using Rssdp.Infrastructure;
@@ -47,17 +48,20 @@ namespace Emby.Dlna.Ssdp
private SsdpDeviceLocator _deviceLocator; private SsdpDeviceLocator _deviceLocator;
private readonly ITimerFactory _timerFactory;
private readonly ISocketFactory _socketFactory; private readonly ISocketFactory _socketFactory;
private ISsdpCommunicationsServer _commsServer; private ISsdpCommunicationsServer _commsServer;
public DeviceDiscovery( public DeviceDiscovery(
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IServerConfigurationManager config, IServerConfigurationManager config,
ISocketFactory socketFactory) ISocketFactory socketFactory,
ITimerFactory timerFactory)
{ {
_logger = loggerFactory.CreateLogger(nameof(DeviceDiscovery)); _logger = loggerFactory.CreateLogger(nameof(DeviceDiscovery));
_config = config; _config = config;
_socketFactory = socketFactory; _socketFactory = socketFactory;
_timerFactory = timerFactory;
} }
// Call this method from somewhere in your code to start the search. // Call this method from somewhere in your code to start the search.
@@ -74,7 +78,7 @@ namespace Emby.Dlna.Ssdp
{ {
if (_listenerCount > 0 && _deviceLocator == null) if (_listenerCount > 0 && _deviceLocator == null)
{ {
_deviceLocator = new SsdpDeviceLocator(_commsServer); _deviceLocator = new SsdpDeviceLocator(_commsServer, _timerFactory);
// (Optional) Set the filter so we only see notifications for devices we care about // (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 // (can be any search target value i.e device type, uuid value etc - any value that appears in the
+6
View File
@@ -5,6 +5,12 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup> </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> <ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
+50 -27
View File
@@ -18,6 +18,7 @@ using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using SkiaSharp;
namespace Emby.Drawing namespace Emby.Drawing
{ {
@@ -65,7 +66,7 @@ namespace Emby.Drawing
_appPaths = appPaths; _appPaths = appPaths;
ImageEnhancers = Array.Empty<IImageEnhancer>(); ImageEnhancers = Array.Empty<IImageEnhancer>();
ImageHelper.ImageProcessor = this; ImageHelper.ImageProcessor = this;
} }
@@ -83,8 +84,8 @@ namespace Emby.Drawing
} }
} }
public IReadOnlyCollection<string> SupportedInputFormats => public string[] SupportedInputFormats =>
new HashSet<string>(StringComparer.OrdinalIgnoreCase) new string[]
{ {
"tiff", "tiff",
"tif", "tif",
@@ -137,14 +138,14 @@ namespace Emby.Drawing
} }
} }
public IReadOnlyCollection<ImageFormat> GetSupportedImageOutputFormats() public ImageFormat[] GetSupportedImageOutputFormats()
=> _imageEncoder.SupportedOutputFormats; {
return _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) public bool SupportsTransparency(string path)
=> TransparentImageTypes.Contains(Path.GetExtension(path)); => TransparentImageTypes.Contains(Path.GetExtension(path).ToLower());
public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options) public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
{ {
@@ -167,10 +168,10 @@ namespace Emby.Drawing
string originalImagePath = originalImage.Path; string originalImagePath = originalImage.Path;
DateTime dateModified = originalImage.DateModified; DateTime dateModified = originalImage.DateModified;
ImageDimensions? originalImageSize = null; ImageSize? originalImageSize = null;
if (originalImage.Width > 0 && originalImage.Height > 0) if (originalImage.Width > 0 && originalImage.Height > 0)
{ {
originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height); originalImageSize = new ImageSize(originalImage.Width, originalImage.Height);
} }
if (!_imageEncoder.SupportsImageEncoding) if (!_imageEncoder.SupportsImageEncoding)
@@ -230,7 +231,7 @@ namespace Emby.Drawing
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
} }
ImageDimensions newSize = ImageHelper.GetNewImageSize(options, null); ImageSize newSize = ImageHelper.GetNewImageSize(options, null);
int quality = options.Quality; int quality = options.Quality;
ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency); ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency);
@@ -244,7 +245,7 @@ namespace Emby.Drawing
try try
{ {
if (!File.Exists(cacheFilePath)) if (!_fileSystem.FileExists(cacheFilePath))
{ {
if (options.CropWhiteSpace && !SupportsTransparency(originalImagePath)) if (options.CropWhiteSpace && !SupportsTransparency(originalImagePath))
{ {
@@ -261,6 +262,15 @@ namespace Emby.Drawing
return (cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath)); 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) catch (Exception ex)
{ {
// If it fails for whatever reason, return the original image // If it fails for whatever reason, return the original image
@@ -324,7 +334,7 @@ namespace Emby.Drawing
/// <summary> /// <summary>
/// Gets the cache file path based on a set of parameters /// Gets the cache file path based on a set of parameters
/// </summary> /// </summary>
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) 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)
{ {
var filename = originalPath var filename = originalPath
+ "width=" + outputSize.Width + "width=" + outputSize.Width
@@ -365,28 +375,29 @@ namespace Emby.Drawing
filename += "v=" + Version; filename += "v=" + Version;
return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLowerInvariant()); return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLower());
} }
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info) public ImageSize GetImageSize(BaseItem item, ItemImageInfo info)
=> GetImageDimensions(item, info, true); => GetImageSize(item, info, true);
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem) public ImageSize GetImageSize(BaseItem item, ItemImageInfo info, bool updateItem)
{ {
int width = info.Width; int width = info.Width;
int height = info.Height; int height = info.Height;
if (height > 0 && width > 0) if (height > 0 && width > 0)
{ {
return new ImageDimensions(width, height); return new ImageSize(width, height);
} }
string path = info.Path; string path = info.Path;
_logger.LogInformation("Getting image size for item {ItemType} {Path}", item.GetType().Name, path); _logger.LogInformation("Getting image size for item {ItemType} {Path}", item.GetType().Name, path);
ImageDimensions size = GetImageDimensions(path); var size = GetImageSize(path);
info.Width = size.Width;
info.Height = size.Height; info.Height = Convert.ToInt32(size.Height);
info.Width = Convert.ToInt32(size.Width);
if (updateItem) if (updateItem)
{ {
@@ -399,8 +410,20 @@ namespace Emby.Drawing
/// <summary> /// <summary>
/// Gets the size of the image. /// Gets the size of the image.
/// </summary> /// </summary>
public ImageDimensions GetImageDimensions(string path) public ImageSize GetImageSize(string path)
=> _imageEncoder.GetImageSize(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);
}
}
/// <summary> /// <summary>
/// Gets the image cache tag. /// Gets the image cache tag.
@@ -472,7 +495,7 @@ namespace Emby.Drawing
return (originalImagePath, dateModified); return (originalImagePath, dateModified);
} }
if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat)) if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat, StringComparer.OrdinalIgnoreCase))
{ {
try try
{ {
@@ -617,12 +640,12 @@ namespace Emby.Drawing
try try
{ {
// Check again in case of contention // Check again in case of contention
if (File.Exists(enhancedImagePath)) if (_fileSystem.FileExists(enhancedImagePath))
{ {
return (enhancedImagePath, treatmentRequiresTransparency); return (enhancedImagePath, treatmentRequiresTransparency);
} }
Directory.CreateDirectory(Path.GetDirectoryName(enhancedImagePath)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(enhancedImagePath));
await ExecuteImageEnhancers(supportedEnhancers, originalImagePath, enhancedImagePath, item, imageType, imageIndex).ConfigureAwait(false); await ExecuteImageEnhancers(supportedEnhancers, originalImagePath, enhancedImagePath, item, imageType, imageIndex).ConfigureAwait(false);
+9 -6
View File
@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
@@ -7,11 +6,15 @@ namespace Emby.Drawing
{ {
public class NullImageEncoder : IImageEncoder public class NullImageEncoder : IImageEncoder
{ {
public IReadOnlyCollection<string> SupportedInputFormats public string[] SupportedInputFormats =>
=> new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "png", "jpeg", "jpg" }; new[]
{
"png",
"jpeg",
"jpg"
};
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats public ImageFormat[] SupportedOutputFormats => new[] { ImageFormat.Jpg, ImageFormat.Png };
=> new HashSet<ImageFormat>() { ImageFormat.Jpg, ImageFormat.Png };
public void CropWhiteSpace(string inputPath, string outputPath) public void CropWhiteSpace(string inputPath, string outputPath)
{ {
@@ -34,7 +37,7 @@ namespace Emby.Drawing
public bool SupportsImageEncoding => false; public bool SupportsImageEncoding => false;
public ImageDimensions GetImageSize(string path) public ImageSize GetImageSize(string path)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@@ -2,13 +2,13 @@ using System;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using SkiaSharp; using SkiaSharp;
namespace Jellyfin.Drawing.Skia namespace Emby.Drawing
{ {
public static class PercentPlayedDrawer public static class PercentPlayedDrawer
{ {
private const int IndicatorHeight = 8; private const int IndicatorHeight = 8;
public static void Process(SKCanvas canvas, ImageDimensions imageSize, double percent) public static void Process(SKCanvas canvas, ImageSize imageSize, double percent)
{ {
using (var paint = new SKPaint()) using (var paint = new SKPaint())
{ {
@@ -24,7 +24,7 @@ namespace Jellyfin.Drawing.Skia
foregroundWidth /= 100; foregroundWidth /= 100;
paint.Color = SKColor.Parse("#FF52B54B"); paint.Color = SKColor.Parse("#FF52B54B");
canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, Convert.ToInt32(foregroundWidth), (float)endY), paint); canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, Convert.ToInt32(Math.Round(foregroundWidth)), (float)endY), paint);
} }
} }
} }
@@ -1,13 +1,13 @@
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using SkiaSharp; using SkiaSharp;
namespace Jellyfin.Drawing.Skia namespace Emby.Drawing
{ {
public static class PlayedIndicatorDrawer public static class PlayedIndicatorDrawer
{ {
private const int OffsetFromTopRightCorner = 38; private const int OffsetFromTopRightCorner = 38;
public static void DrawPlayedIndicator(SKCanvas canvas, ImageDimensions imageSize) public static void DrawPlayedIndicator(SKCanvas canvas, ImageSize imageSize)
{ {
var x = imageSize.Width - OffsetFromTopRightCorner; var x = imageSize.Width - OffsetFromTopRightCorner;
@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -13,7 +12,7 @@ using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using SkiaSharp; using SkiaSharp;
namespace Jellyfin.Drawing.Skia namespace Emby.Drawing
{ {
public class SkiaEncoder : IImageEncoder public class SkiaEncoder : IImageEncoder
{ {
@@ -36,8 +35,8 @@ namespace Jellyfin.Drawing.Skia
LogVersion(); LogVersion();
} }
public IReadOnlyCollection<string> SupportedInputFormats => public string[] SupportedInputFormats =>
new HashSet<string>(StringComparer.OrdinalIgnoreCase) new[]
{ {
"jpeg", "jpeg",
"jpg", "jpg",
@@ -63,8 +62,7 @@ namespace Jellyfin.Drawing.Skia
"arw" "arw"
}; };
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats public ImageFormat[] SupportedOutputFormats => new[] { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
=> new HashSet<ImageFormat>() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
private void LogVersion() private void LogVersion()
{ {
@@ -74,11 +72,16 @@ namespace Jellyfin.Drawing.Skia
_logger.LogInformation("SkiaSharp version: " + GetVersion()); _logger.LogInformation("SkiaSharp version: " + GetVersion());
} }
public static Version GetVersion() public static string GetVersion()
=> typeof(SKBitmap).GetTypeInfo().Assembly.GetName().Version; {
return typeof(SKBitmap).GetTypeInfo().Assembly.GetName().Version.ToString();
}
private static bool IsTransparent(SKColor color) private static bool IsTransparent(SKColor color)
=> (color.Red == 255 && color.Green == 255 && color.Blue == 255) || color.Alpha == 0; {
return (color.Red == 255 && color.Green == 255 && color.Blue == 255) || color.Alpha == 0;
}
public static SKEncodedImageFormat GetImageFormat(ImageFormat selectedFormat) public static SKEncodedImageFormat GetImageFormat(ImageFormat selectedFormat)
{ {
@@ -127,51 +130,33 @@ namespace Jellyfin.Drawing.Skia
for (int row = 0; row < bitmap.Height; ++row) for (int row = 0; row < bitmap.Height; ++row)
{ {
if (IsTransparentRow(bitmap, row)) if (IsTransparentRow(bitmap, row))
{
topmost = row + 1; topmost = row + 1;
} else break;
else
{
break;
}
} }
int bottommost = bitmap.Height; int bottommost = bitmap.Height;
for (int row = bitmap.Height - 1; row >= 0; --row) for (int row = bitmap.Height - 1; row >= 0; --row)
{ {
if (IsTransparentRow(bitmap, row)) if (IsTransparentRow(bitmap, row))
{
bottommost = row; bottommost = row;
} else break;
else
{
break;
}
} }
int leftmost = 0, rightmost = bitmap.Width; int leftmost = 0, rightmost = bitmap.Width;
for (int col = 0; col < bitmap.Width; ++col) for (int col = 0; col < bitmap.Width; ++col)
{ {
if (IsTransparentColumn(bitmap, col)) if (IsTransparentColumn(bitmap, col))
{
leftmost = col + 1; leftmost = col + 1;
}
else else
{
break; break;
}
} }
for (int col = bitmap.Width - 1; col >= 0; --col) for (int col = bitmap.Width - 1; col >= 0; --col)
{ {
if (IsTransparentColumn(bitmap, col)) if (IsTransparentColumn(bitmap, col))
{
rightmost = col; rightmost = col;
}
else else
{
break; break;
}
} }
var newRect = SKRectI.Create(leftmost, topmost, rightmost - leftmost, bottommost - topmost); var newRect = SKRectI.Create(leftmost, topmost, rightmost - leftmost, bottommost - topmost);
@@ -183,19 +168,25 @@ namespace Jellyfin.Drawing.Skia
} }
} }
public ImageDimensions GetImageSize(string path) public ImageSize GetImageSize(string path)
{ {
using (var s = new SKFileStream(path)) using (var s = new SKFileStream(path))
using (var codec = SKCodec.Create(s)) using (var codec = SKCodec.Create(s))
{ {
var info = codec.Info; var info = codec.Info;
return new ImageDimensions(info.Width, info.Height); return new ImageSize
{
Width = info.Width,
Height = info.Height
};
} }
} }
private static bool HasDiacritics(string text) private static bool HasDiacritics(string text)
=> !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal); {
return !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal);
}
private static bool RequiresSpecialCharacterHack(string path) private static bool RequiresSpecialCharacterHack(string path)
{ {
@@ -221,8 +212,8 @@ namespace Jellyfin.Drawing.Skia
var tempPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + Path.GetExtension(path) ?? string.Empty); var tempPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + Path.GetExtension(path) ?? string.Empty);
Directory.CreateDirectory(Path.GetDirectoryName(tempPath)); fileSystem.CreateDirectory(fileSystem.GetDirectoryName(tempPath));
File.Copy(path, tempPath, true); fileSystem.CopyFile(path, tempPath, true);
return tempPath; return tempPath;
} }
@@ -255,17 +246,15 @@ namespace Jellyfin.Drawing.Skia
} }
} }
private static readonly HashSet<string> TransparentImageTypes private static string[] TransparentImageTypes = new string[] { ".png", ".gif", ".webp" };
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
internal static SKBitmap Decode(string path, bool forceCleanBitmap, IFileSystem fileSystem, ImageOrientation? orientation, out SKEncodedOrigin origin) internal static SKBitmap Decode(string path, bool forceCleanBitmap, IFileSystem fileSystem, ImageOrientation? orientation, out SKEncodedOrigin origin)
{ {
if (!File.Exists(path)) if (!fileSystem.FileExists(path))
{ {
throw new FileNotFoundException("File not found", path); throw new FileNotFoundException("File not found", path);
} }
var requiresTransparencyHack = TransparentImageTypes.Contains(Path.GetExtension(path)); var requiresTransparencyHack = TransparentImageTypes.Contains(Path.GetExtension(path) ?? string.Empty);
if (requiresTransparencyHack || forceCleanBitmap) if (requiresTransparencyHack || forceCleanBitmap)
{ {
@@ -281,10 +270,17 @@ namespace Jellyfin.Drawing.Skia
// create the bitmap // create the bitmap
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack); var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
// decode if (bitmap != null)
var _ = codec.GetPixels(bitmap.Info, bitmap.GetPixels()); {
// decode
codec.GetPixels(bitmap.Info, bitmap.GetPixels());
origin = codec.EncodedOrigin; origin = codec.EncodedOrigin;
}
else
{
origin = GetSKEncodedOrigin(orientation);
}
return bitmap; return bitmap;
} }
@@ -331,11 +327,14 @@ namespace Jellyfin.Drawing.Skia
{ {
var bitmap = GetBitmap(path, cropWhitespace, true, orientation, out origin); var bitmap = GetBitmap(path, cropWhitespace, true, orientation, out origin);
if (bitmap != null && origin != SKEncodedOrigin.TopLeft) if (bitmap != null)
{ {
using (bitmap) if (origin != SKEncodedOrigin.TopLeft)
{ {
return OrientImage(bitmap, origin); using (bitmap)
{
return OrientImage(bitmap, origin);
}
} }
} }
@@ -359,6 +358,7 @@ namespace Jellyfin.Drawing.Skia
switch (origin) switch (origin)
{ {
case SKEncodedOrigin.TopRight: case SKEncodedOrigin.TopRight:
{ {
var rotated = new SKBitmap(bitmap.Width, bitmap.Height); var rotated = new SKBitmap(bitmap.Width, bitmap.Height);
@@ -377,8 +377,11 @@ namespace Jellyfin.Drawing.Skia
var rotated = new SKBitmap(bitmap.Width, bitmap.Height); var rotated = new SKBitmap(bitmap.Width, bitmap.Height);
using (var surface = new SKCanvas(rotated)) using (var surface = new SKCanvas(rotated))
{ {
float px = (float)bitmap.Width / 2; float px = bitmap.Width;
float py = (float)bitmap.Height / 2; px /= 2;
float py = bitmap.Height;
py /= 2;
surface.RotateDegrees(180, px, py); surface.RotateDegrees(180, px, py);
surface.DrawBitmap(bitmap, 0, 0); surface.DrawBitmap(bitmap, 0, 0);
@@ -392,9 +395,11 @@ namespace Jellyfin.Drawing.Skia
var rotated = new SKBitmap(bitmap.Width, bitmap.Height); var rotated = new SKBitmap(bitmap.Width, bitmap.Height);
using (var surface = new SKCanvas(rotated)) using (var surface = new SKCanvas(rotated))
{ {
float px = (float)bitmap.Width / 2; float px = bitmap.Width;
px /= 2;
float py = (float)bitmap.Height / 2; float py = bitmap.Height;
py /= 2;
surface.Translate(rotated.Width, 0); surface.Translate(rotated.Width, 0);
surface.Scale(-1, 1); surface.Scale(-1, 1);
@@ -418,6 +423,7 @@ namespace Jellyfin.Drawing.Skia
surface.RotateDegrees(90); surface.RotateDegrees(90);
surface.DrawBitmap(bitmap, 0, 0); surface.DrawBitmap(bitmap, 0, 0);
} }
var flippedBitmap = new SKBitmap(rotated.Width, rotated.Height); var flippedBitmap = new SKBitmap(rotated.Width, rotated.Height);
@@ -482,7 +488,8 @@ namespace Jellyfin.Drawing.Skia
return rotated; return rotated;
} }
default: return bitmap; default:
return bitmap;
} }
} }
@@ -492,7 +499,6 @@ namespace Jellyfin.Drawing.Skia
{ {
throw new ArgumentNullException(nameof(inputPath)); throw new ArgumentNullException(nameof(inputPath));
} }
if (string.IsNullOrWhiteSpace(inputPath)) if (string.IsNullOrWhiteSpace(inputPath))
{ {
throw new ArgumentNullException(nameof(outputPath)); throw new ArgumentNullException(nameof(outputPath));
@@ -512,11 +518,11 @@ namespace Jellyfin.Drawing.Skia
throw new ArgumentOutOfRangeException(string.Format("Skia unable to read image {0}", inputPath)); throw new ArgumentOutOfRangeException(string.Format("Skia unable to read image {0}", inputPath));
} }
var originalImageSize = new ImageDimensions(bitmap.Width, bitmap.Height); //_logger.LogInformation("Color type {0}", bitmap.Info.ColorType);
if (!options.CropWhiteSpace var originalImageSize = new ImageSize(bitmap.Width, bitmap.Height);
&& options.HasDefaultOptions(inputPath, originalImageSize)
&& !autoOrient) if (!options.CropWhiteSpace && options.HasDefaultOptions(inputPath, originalImageSize) && !autoOrient)
{ {
// Just spit out the original file if all the options are default // Just spit out the original file if all the options are default
return inputPath; return inputPath;
@@ -524,10 +530,10 @@ namespace Jellyfin.Drawing.Skia
var newImageSize = ImageHelper.GetNewImageSize(options, originalImageSize); var newImageSize = ImageHelper.GetNewImageSize(options, originalImageSize);
var width = newImageSize.Width; var width = Convert.ToInt32(Math.Round(newImageSize.Width));
var height = newImageSize.Height; var height = Convert.ToInt32(Math.Round(newImageSize.Height));
using (var resizedBitmap = new SKBitmap(width, height, bitmap.ColorType, bitmap.AlphaType)) using (var resizedBitmap = new SKBitmap(width, height))//, bitmap.ColorType, bitmap.AlphaType))
{ {
// scale image // scale image
bitmap.ScalePixels(resizedBitmap, SKFilterQuality.High); bitmap.ScalePixels(resizedBitmap, SKFilterQuality.High);
@@ -535,12 +541,14 @@ namespace Jellyfin.Drawing.Skia
// If all we're doing is resizing then we can stop now // If all we're doing is resizing then we can stop now
if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator)
{ {
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath));
using (var outputStream = new SKFileWStream(outputPath)) using (var outputStream = new SKFileWStream(outputPath))
using (var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels()))
{ {
pixmap.Encode(outputStream, skiaOutputFormat, quality); using (var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels()))
return outputPath; {
pixmap.Encode(outputStream, skiaOutputFormat, quality);
return outputPath;
}
} }
} }
@@ -587,7 +595,7 @@ namespace Jellyfin.Drawing.Skia
DrawIndicator(canvas, width, height, options); DrawIndicator(canvas, width, height, options);
} }
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath));
using (var outputStream = new SKFileWStream(outputPath)) using (var outputStream = new SKFileWStream(outputPath))
{ {
using (var pixmap = new SKPixmap(new SKImageInfo(width, height), saveBitmap.GetPixels())) using (var pixmap = new SKPixmap(new SKImageInfo(width, height), saveBitmap.GetPixels()))
@@ -603,7 +611,8 @@ namespace Jellyfin.Drawing.Skia
public void CreateImageCollage(ImageCollageOptions options) public void CreateImageCollage(ImageCollageOptions options)
{ {
double ratio = (double)options.Width / options.Height; double ratio = options.Width;
ratio /= options.Height;
if (ratio >= 1.4) if (ratio >= 1.4)
{ {
@@ -615,7 +624,7 @@ namespace Jellyfin.Drawing.Skia
} }
else else
{ {
// TODO: Create Poster collage capability // @todo create Poster collage capability
new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths, options.OutputPath, options.Width, options.Height);
} }
} }
@@ -624,7 +633,7 @@ namespace Jellyfin.Drawing.Skia
{ {
try try
{ {
var currentImageSize = new ImageDimensions(imageWidth, imageHeight); var currentImageSize = new ImageSize(imageWidth, imageHeight);
if (options.AddPlayedIndicator) if (options.AddPlayedIndicator)
{ {
@@ -5,7 +5,7 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using SkiaSharp; using SkiaSharp;
namespace Jellyfin.Drawing.Skia namespace Emby.Drawing
{ {
public class StripCollageBuilder public class StripCollageBuilder
{ {
@@ -25,7 +25,7 @@ namespace Jellyfin.Drawing.Skia
throw new ArgumentNullException(nameof(outputPath)); throw new ArgumentNullException(nameof(outputPath));
} }
var ext = Path.GetExtension(outputPath).ToLowerInvariant(); var ext = Path.GetExtension(outputPath).ToLower();
if (ext == ".jpg" || ext == ".jpeg") if (ext == ".jpg" || ext == ".jpeg")
return SKEncodedImageFormat.Jpeg; return SKEncodedImageFormat.Jpeg;
@@ -43,14 +43,21 @@ namespace Jellyfin.Drawing.Skia
return SKEncodedImageFormat.Png; return SKEncodedImageFormat.Png;
} }
public void BuildPosterCollage(string[] paths, string outputPath, int width, int height)
{
// @todo
}
public void BuildSquareCollage(string[] paths, string outputPath, int width, int height) public void BuildSquareCollage(string[] paths, string outputPath, int width, int height)
{ {
using (var bitmap = BuildSquareCollageBitmap(paths, width, height)) using (var bitmap = BuildSquareCollageBitmap(paths, width, height))
using (var outputStream = new SKFileWStream(outputPath))
{ {
using (var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels())) using (var outputStream = new SKFileWStream(outputPath))
{ {
pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); using (var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels()))
{
pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90);
}
} }
} }
} }
@@ -2,13 +2,13 @@ using System.Globalization;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using SkiaSharp; using SkiaSharp;
namespace Jellyfin.Drawing.Skia namespace Emby.Drawing
{ {
public static class UnplayedCountIndicator public static class UnplayedCountIndicator
{ {
private const int OffsetFromTopRightCorner = 38; private const int OffsetFromTopRightCorner = 38;
public static void DrawUnplayedCountIndicator(SKCanvas canvas, ImageDimensions imageSize, int count) public static void DrawUnplayedCountIndicator(SKCanvas canvas, ImageSize imageSize, int count)
{ {
var x = imageSize.Width - OffsetFromTopRightCorner; var x = imageSize.Width - OffsetFromTopRightCorner;
var text = count.ToString(CultureInfo.InvariantCulture); var text = count.ToString(CultureInfo.InvariantCulture);
+10 -9
View File
@@ -39,7 +39,7 @@ namespace IsoMounter
_logger = logger; _logger = logger;
ProcessFactory = processFactory; ProcessFactory = processFactory;
MountPointRoot = Path.DirectorySeparatorChar + "tmp" + Path.DirectorySeparatorChar + "Emby"; MountPointRoot = FileSystem.DirectorySeparatorChar + "tmp" + FileSystem.DirectorySeparatorChar + "Emby";
_logger.LogDebug( _logger.LogDebug(
"[{0}] System PATH is currently set to [{1}].", "[{0}] System PATH is currently set to [{1}].",
@@ -121,7 +121,7 @@ namespace IsoMounter
path, path,
Path.GetExtension(path), Path.GetExtension(path),
EnvironmentInfo.OperatingSystem, EnvironmentInfo.OperatingSystem,
ExecutablesAvailable ExecutablesAvailable.ToString()
); );
if (ExecutablesAvailable) if (ExecutablesAvailable)
@@ -183,7 +183,7 @@ namespace IsoMounter
_logger.LogInformation( _logger.LogInformation(
"[{0}] Disposing [{1}].", "[{0}] Disposing [{1}].",
Name, Name,
disposing disposing.ToString()
); );
if (disposing) if (disposing)
@@ -214,9 +214,9 @@ namespace IsoMounter
{ {
string path = test.Trim(); string path = test.Trim();
if (!string.IsNullOrEmpty(path) && File.Exists(path = Path.Combine(path, name))) if (!string.IsNullOrEmpty(path) && FileSystem.FileExists(path = Path.Combine(path, name)))
{ {
return Path.GetFullPath(path); return FileSystem.GetFullPath(path);
} }
} }
@@ -229,8 +229,9 @@ namespace IsoMounter
var uid = getuid(); var uid = getuid();
_logger.LogDebug( _logger.LogDebug(
"[{0}] GetUserId() returned [{2}].", "[{0}] Our current UID is [{1}], GetUserId() returned [{2}].",
Name, Name,
uid.ToString(),
uid uid
); );
@@ -326,7 +327,7 @@ namespace IsoMounter
try try
{ {
Directory.CreateDirectory(mountPoint); FileSystem.CreateDirectory(mountPoint);
} }
catch (UnauthorizedAccessException) catch (UnauthorizedAccessException)
{ {
@@ -376,7 +377,7 @@ namespace IsoMounter
try try
{ {
Directory.Delete(mountPoint, false); FileSystem.DeleteDirectory(mountPoint, false);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -454,7 +455,7 @@ namespace IsoMounter
try try
{ {
Directory.Delete(mount.MountedPath, false); FileSystem.DeleteDirectory(mount.MountedPath, false);
} }
catch (Exception ex) catch (Exception ex)
{ {
+2 -5
View File
@@ -30,13 +30,10 @@ namespace Emby.Naming.AudioBook
{ {
throw new ArgumentNullException(nameof(path)); throw new ArgumentNullException(nameof(path));
} }
if (IsDirectory)
if (IsDirectory) // TODO
{
return null; return null;
}
var extension = Path.GetExtension(path); var extension = Path.GetExtension(path) ?? string.Empty;
// Check supported extensions // Check supported extensions
if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{ {
+26 -5
View File
@@ -183,7 +183,8 @@ namespace Emby.Naming.Video
{ {
if (videos.All(i => i.Files.Count == 1 && IsEligibleForMultiVersion(folderName, i.Files[0].Path))) if (videos.All(i => i.Files.Count == 1 && IsEligibleForMultiVersion(folderName, i.Files[0].Path)))
{ {
if (HaveSameYear(videos)) // Enforce the multi-version limit
if (videos.Count <= 8 && HaveSameYear(videos))
{ {
var ordered = videos.OrderBy(i => i.Name).ToList(); var ordered = videos.OrderBy(i => i.Name).ToList();
@@ -199,6 +200,23 @@ namespace Emby.Naming.Video
} }
return videos; 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) private bool HaveSameYear(List<VideoInfo> videos)
@@ -208,14 +226,17 @@ namespace Emby.Naming.Video
private bool IsEligibleForMultiVersion(string folderName, string testFilename) private bool IsEligibleForMultiVersion(string folderName, string testFilename)
{ {
testFilename = Path.GetFileNameWithoutExtension(testFilename) ?? string.Empty; testFilename = Path.GetFileNameWithoutExtension(testFilename);
if (string.Equals(folderName, testFilename, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase)) if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
{ {
testFilename = testFilename.Substring(folderName.Length).Trim(); testFilename = testFilename.Substring(folderName.Length).Trim();
return string.IsNullOrEmpty(testFilename) || return testFilename.StartsWith("-", StringComparison.OrdinalIgnoreCase) || Regex.Replace(testFilename, @"\[([^]]*)\]", "").Trim() == string.Empty;
testFilename.StartsWith("-") ||
string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)) ;
} }
return false; return false;
@@ -73,6 +73,11 @@ namespace Emby.Notifications
Type = NotificationType.AudioPlayback.ToString() Type = NotificationType.AudioPlayback.ToString()
}, },
new NotificationTypeInfo
{
Type = NotificationType.GamePlayback.ToString()
},
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.VideoPlayback.ToString() Type = NotificationType.VideoPlayback.ToString()
@@ -83,6 +88,11 @@ namespace Emby.Notifications
Type = NotificationType.AudioPlaybackStopped.ToString() Type = NotificationType.AudioPlaybackStopped.ToString()
}, },
new NotificationTypeInfo
{
Type = NotificationType.GamePlaybackStopped.ToString()
},
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.VideoPlaybackStopped.ToString() Type = NotificationType.VideoPlaybackStopped.ToString()
+7 -6
View File
@@ -20,6 +20,7 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Notifications; using MediaBrowser.Model.Notifications;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Notifications namespace Emby.Notifications
@@ -39,8 +40,9 @@ namespace Emby.Notifications
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly ITimerFactory _timerFactory;
private Timer LibraryUpdateTimer { get; set; } private ITimer LibraryUpdateTimer { get; set; }
private readonly object _libraryChangedSyncLock = new object(); private readonly object _libraryChangedSyncLock = new object();
private readonly IConfigurationManager _config; private readonly IConfigurationManager _config;
@@ -50,7 +52,7 @@ namespace Emby.Notifications
private string[] _coreNotificationTypes; private string[] _coreNotificationTypes;
public Notifications(IInstallationManager installationManager, IActivityManager activityManager, ILocalizationManager localization, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost, IConfigurationManager config, IDeviceManager deviceManager) public Notifications(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)
{ {
_installationManager = installationManager; _installationManager = installationManager;
_userManager = userManager; _userManager = userManager;
@@ -62,20 +64,19 @@ namespace Emby.Notifications
_appHost = appHost; _appHost = appHost;
_config = config; _config = config;
_deviceManager = deviceManager; _deviceManager = deviceManager;
_timerFactory = timerFactory;
_localization = localization; _localization = localization;
_activityManager = activityManager; _activityManager = activityManager;
_coreNotificationTypes = new CoreNotificationTypes(localization, appHost).GetNotificationTypes().Select(i => i.Type).ToArray(); _coreNotificationTypes = new CoreNotificationTypes(localization, appHost).GetNotificationTypes().Select(i => i.Type).ToArray();
} }
public Task RunAsync() public void Run()
{ {
_libraryManager.ItemAdded += _libraryManager_ItemAdded; _libraryManager.ItemAdded += _libraryManager_ItemAdded;
_appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged; _appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged;
_appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged; _appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged;
_activityManager.EntryCreated += _activityManager_EntryCreated; _activityManager.EntryCreated += _activityManager_EntryCreated;
return Task.CompletedTask;
} }
private async void _appHost_HasPendingRestartChanged(object sender, EventArgs e) private async void _appHost_HasPendingRestartChanged(object sender, EventArgs e)
@@ -156,7 +157,7 @@ namespace Emby.Notifications
{ {
if (LibraryUpdateTimer == null) if (LibraryUpdateTimer == null)
{ {
LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, 5000, LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, 5000,
Timeout.Infinite); Timeout.Infinite);
} }
else else
+1 -4
View File
@@ -3,16 +3,13 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\ThirdParty\taglib-sharp\src\taglib-sharp.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\SharedVersion.cs" /> <Compile Include="..\SharedVersion.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="TagLibSharp" Version="2.2.0-beta" />
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+3 -3
View File
@@ -181,12 +181,12 @@ namespace Emby.Photos
try try
{ {
var size = _imageProcessor.GetImageDimensions(item, img, false); var size = _imageProcessor.GetImageSize(item, img, false);
if (size.Width > 0 && size.Height > 0) if (size.Width > 0 && size.Height > 0)
{ {
item.Width = size.Width; item.Width = Convert.ToInt32(size.Width);
item.Height = size.Height; item.Height = Convert.ToInt32(size.Height);
} }
} }
catch (ArgumentException) catch (ArgumentException)
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates; using MediaBrowser.Common.Updates;
@@ -59,7 +58,7 @@ namespace Emby.Server.Implementations.Activity
_deviceManager = deviceManager; _deviceManager = deviceManager;
} }
public Task RunAsync() public void Run()
{ {
_taskManager.TaskCompleted += _taskManager_TaskCompleted; _taskManager.TaskCompleted += _taskManager_TaskCompleted;
@@ -91,8 +90,6 @@ namespace Emby.Server.Implementations.Activity
_deviceManager.CameraImageUploaded += _deviceManager_CameraImageUploaded; _deviceManager.CameraImageUploaded += _deviceManager_CameraImageUploaded;
_appHost.ApplicationUpdated += _appHost_ApplicationUpdated; _appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
return Task.CompletedTask;
} }
void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e) void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
@@ -210,6 +207,10 @@ namespace Emby.Server.Implementations.Activity
{ {
return NotificationType.AudioPlayback.ToString(); return NotificationType.AudioPlayback.ToString();
} }
if (string.Equals(mediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase))
{
return NotificationType.GamePlayback.ToString();
}
if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{ {
return NotificationType.VideoPlayback.ToString(); return NotificationType.VideoPlayback.ToString();
@@ -224,6 +225,10 @@ namespace Emby.Server.Implementations.Activity
{ {
return NotificationType.AudioPlaybackStopped.ToString(); return NotificationType.AudioPlaybackStopped.ToString();
} }
if (string.Equals(mediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase))
{
return NotificationType.GamePlaybackStopped.ToString();
}
if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{ {
return NotificationType.VideoPlaybackStopped.ToString(); return NotificationType.VideoPlaybackStopped.ToString();
@@ -496,6 +501,7 @@ namespace Emby.Server.Implementations.Activity
_sessionManager.PlaybackStart -= _sessionManager_PlaybackStart; _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart;
_sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped;
_subManager.SubtitlesDownloaded -= _subManager_SubtitlesDownloaded;
_subManager.SubtitleDownloadFailure -= _subManager_SubtitleDownloadFailure; _subManager.SubtitleDownloadFailure -= _subManager_SubtitleDownloadFailure;
_userManager.UserCreated -= _userManager_UserCreated; _userManager.UserCreated -= _userManager_UserCreated;
@@ -16,14 +16,12 @@ namespace Emby.Server.Implementations.AppBase
string programDataPath, string programDataPath,
string appFolderPath, string appFolderPath,
string logDirectoryPath = null, string logDirectoryPath = null,
string configurationDirectoryPath = null, string configurationDirectoryPath = null)
string cacheDirectoryPath = null)
{ {
ProgramDataPath = programDataPath; ProgramDataPath = programDataPath;
ProgramSystemPath = appFolderPath; ProgramSystemPath = appFolderPath;
LogDirectoryPath = logDirectoryPath; LogDirectoryPath = logDirectoryPath;
ConfigurationDirectoryPath = configurationDirectoryPath; ConfigurationDirectoryPath = configurationDirectoryPath;
CachePath = cacheDirectoryPath;
} }
public string ProgramDataPath { get; private set; } public string ProgramDataPath { get; private set; }
@@ -127,7 +127,7 @@ namespace Emby.Server.Implementations.AppBase
Logger.LogInformation("Saving system configuration"); Logger.LogInformation("Saving system configuration");
var path = CommonApplicationPaths.SystemConfigurationFilePath; var path = CommonApplicationPaths.SystemConfigurationFilePath;
Directory.CreateDirectory(Path.GetDirectoryName(path)); FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path));
lock (_configurationSyncLock) lock (_configurationSyncLock)
{ {
@@ -171,29 +171,16 @@ namespace Emby.Server.Implementations.AppBase
private void UpdateCachePath() private void UpdateCachePath()
{ {
string cachePath; string cachePath;
// If the configuration file has no entry (i.e. not set in UI)
if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath)) if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath))
{ {
// If the current live configuration has no entry (i.e. not set on CLI/envvars, during startup) cachePath = null;
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 else
{ {
// Set cachePath to the new UI-set value cachePath = Path.Combine(CommonConfiguration.CachePath, "cache");
cachePath = CommonConfiguration.CachePath;
} }
Logger.LogInformation("Setting cache path to " + cachePath);
((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath; ((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath;
} }
@@ -210,7 +197,7 @@ namespace Emby.Server.Implementations.AppBase
&& !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath)) && !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath))
{ {
// Validate // Validate
if (!Directory.Exists(newPath)) if (!FileSystem.DirectoryExists(newPath))
{ {
throw new FileNotFoundException(string.Format("{0} does not exist.", newPath)); throw new FileNotFoundException(string.Format("{0} does not exist.", newPath));
} }
@@ -222,7 +209,8 @@ namespace Emby.Server.Implementations.AppBase
protected void EnsureWriteAccess(string path) protected void EnsureWriteAccess(string path)
{ {
var file = Path.Combine(path, Guid.NewGuid().ToString()); var file = Path.Combine(path, Guid.NewGuid().ToString());
File.WriteAllText(file, string.Empty);
FileSystem.WriteAllText(file, string.Empty);
FileSystem.DeleteFile(file); FileSystem.DeleteFile(file);
} }
@@ -230,7 +218,7 @@ namespace Emby.Server.Implementations.AppBase
private string GetConfigurationFile(string key) private string GetConfigurationFile(string key)
{ {
return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLowerInvariant() + ".xml"); return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLower() + ".xml");
} }
public object GetConfiguration(string key) public object GetConfiguration(string key)
@@ -258,15 +246,14 @@ namespace Emby.Server.Implementations.AppBase
private object LoadConfiguration(string path, Type configurationType) private object LoadConfiguration(string path, Type configurationType)
{ {
if (!File.Exists(path))
{
return Activator.CreateInstance(configurationType);
}
try try
{ {
return XmlSerializer.DeserializeFromFile(configurationType, path); return XmlSerializer.DeserializeFromFile(configurationType, path);
} }
catch (FileNotFoundException)
{
return Activator.CreateInstance(configurationType);
}
catch (IOException) catch (IOException)
{ {
return Activator.CreateInstance(configurationType); return Activator.CreateInstance(configurationType);
@@ -306,7 +293,7 @@ namespace Emby.Server.Implementations.AppBase
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration); _configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
var path = GetConfigurationFile(key); var path = GetConfigurationFile(key);
Directory.CreateDirectory(Path.GetDirectoryName(path)); FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path));
lock (_configurationSyncLock) lock (_configurationSyncLock)
{ {
@@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.AppBase
// Use try/catch to avoid the extra file system lookup using File.Exists // Use try/catch to avoid the extra file system lookup using File.Exists
try try
{ {
buffer = File.ReadAllBytes(path); buffer = fileSystem.ReadAllBytes(path);
configuration = xmlSerializer.DeserializeFromBytes(type, buffer); 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 the file didn't exist before, or if something has changed, re-save
if (buffer == null || !buffer.SequenceEqual(newBytes)) if (buffer == null || !buffer.SequenceEqual(newBytes))
{ {
Directory.CreateDirectory(Path.GetDirectoryName(path)); fileSystem.CreateDirectory(fileSystem.GetDirectoryName(path));
// Save it after load in case we got new items // Save it after load in case we got new items
File.WriteAllBytes(path, newBytes); fileSystem.WriteAllBytes(path, newBytes);
} }
return configuration; return configuration;
+142 -45
View File
@@ -12,6 +12,7 @@ using System.Security.Cryptography.X509Certificates;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Common.Implementations.Serialization;
using Emby.Dlna; using Emby.Dlna;
using Emby.Dlna.Main; using Emby.Dlna.Main;
using Emby.Dlna.Ssdp; using Emby.Dlna.Ssdp;
@@ -42,6 +43,7 @@ using Emby.Server.Implementations.ScheduledTasks;
using Emby.Server.Implementations.Security; using Emby.Server.Implementations.Security;
using Emby.Server.Implementations.Serialization; using Emby.Server.Implementations.Serialization;
using Emby.Server.Implementations.Session; using Emby.Server.Implementations.Session;
using Emby.Server.Implementations.Threading;
using Emby.Server.Implementations.TV; using Emby.Server.Implementations.TV;
using Emby.Server.Implementations.Updates; using Emby.Server.Implementations.Updates;
using Emby.Server.Implementations.Xml; using Emby.Server.Implementations.Xml;
@@ -97,6 +99,7 @@ using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using MediaBrowser.Model.System; using MediaBrowser.Model.System;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Threading;
using MediaBrowser.Model.Updates; using MediaBrowser.Model.Updates;
using MediaBrowser.Model.Xml; using MediaBrowser.Model.Xml;
using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Chapters;
@@ -107,7 +110,9 @@ using MediaBrowser.XbmcMetadata.Providers;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using ServiceStack; using ServiceStack;
using ServiceStack.Text.Jsv; using ServiceStack.Text.Jsv;
using StringExtensions = MediaBrowser.Controller.Extensions.StringExtensions;
using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate;
using UtfUnknown;
namespace Emby.Server.Implementations namespace Emby.Server.Implementations
{ {
@@ -137,7 +142,7 @@ namespace Emby.Server.Implementations
return false; return false;
} }
if (StartupOptions.IsService) if (StartupOptions.ContainsOption("-service"))
{ {
return false; return false;
} }
@@ -239,6 +244,8 @@ namespace Emby.Server.Implementations
/// </summary> /// </summary>
protected readonly SimpleInjector.Container Container = new SimpleInjector.Container(); protected readonly SimpleInjector.Container Container = new SimpleInjector.Container();
protected ISystemEvents SystemEvents { get; set; }
/// <summary> /// <summary>
/// Gets the server configuration manager. /// Gets the server configuration manager.
/// </summary> /// </summary>
@@ -298,7 +305,7 @@ namespace Emby.Server.Implementations
private ILiveTvManager LiveTvManager { get; set; } private ILiveTvManager LiveTvManager { get; set; }
public LocalizationManager LocalizationManager { get; set; } public ILocalizationManager LocalizationManager { get; set; }
private IEncodingManager EncodingManager { get; set; } private IEncodingManager EncodingManager { get; set; }
private IChannelManager ChannelManager { get; set; } private IChannelManager ChannelManager { get; set; }
@@ -310,7 +317,7 @@ namespace Emby.Server.Implementations
private IUserDataManager UserDataManager { get; set; } private IUserDataManager UserDataManager { get; set; }
private IUserRepository UserRepository { get; set; } private IUserRepository UserRepository { get; set; }
internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; } internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; }
internal SqliteItemRepository ItemRepository { get; set; } internal IItemRepository ItemRepository { get; set; }
private INotificationManager NotificationManager { get; set; } private INotificationManager NotificationManager { get; set; }
private ISubtitleManager SubtitleManager { get; set; } private ISubtitleManager SubtitleManager { get; set; }
@@ -339,11 +346,12 @@ namespace Emby.Server.Implementations
protected IHttpResultFactory HttpResultFactory { get; private set; } protected IHttpResultFactory HttpResultFactory { get; private set; }
protected IAuthService AuthService { get; private set; } protected IAuthService AuthService { get; private set; }
public IStartupOptions StartupOptions { get; private set; } public StartupOptions StartupOptions { get; private set; }
internal IImageEncoder ImageEncoder { get; private set; } internal IImageEncoder ImageEncoder { get; private set; }
protected IProcessFactory ProcessFactory { get; private set; } protected IProcessFactory ProcessFactory { get; private set; }
protected ITimerFactory TimerFactory { get; private set; }
protected ICryptoProvider CryptographyProvider = new CryptographyProvider(); protected ICryptoProvider CryptographyProvider = new CryptographyProvider();
protected readonly IXmlSerializer XmlSerializer; protected readonly IXmlSerializer XmlSerializer;
@@ -359,10 +367,11 @@ namespace Emby.Server.Implementations
/// </summary> /// </summary>
public ApplicationHost(ServerApplicationPaths applicationPaths, public ApplicationHost(ServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IStartupOptions options, StartupOptions options,
IFileSystem fileSystem, IFileSystem fileSystem,
IEnvironmentInfo environmentInfo, IEnvironmentInfo environmentInfo,
IImageEncoder imageEncoder, IImageEncoder imageEncoder,
ISystemEvents systemEvents,
INetworkManager networkManager) INetworkManager networkManager)
{ {
@@ -374,6 +383,7 @@ namespace Emby.Server.Implementations
NetworkManager = networkManager; NetworkManager = networkManager;
networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets; networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
EnvironmentInfo = environmentInfo; EnvironmentInfo = environmentInfo;
SystemEvents = systemEvents;
ApplicationPaths = applicationPaths; ApplicationPaths = applicationPaths;
LoggerFactory = loggerFactory; LoggerFactory = loggerFactory;
@@ -456,8 +466,9 @@ namespace Emby.Server.Implementations
private static Tuple<Assembly, string> GetAssembly(Type type) private static Tuple<Assembly, string> GetAssembly(Type type)
{ {
var assembly = type.GetTypeInfo().Assembly; var assembly = type.GetTypeInfo().Assembly;
string path = null;
return new Tuple<Assembly, string>(assembly, null); return new Tuple<Assembly, string>(assembly, path);
} }
public virtual IStreamHelper CreateStreamHelper() public virtual IStreamHelper CreateStreamHelper()
@@ -564,7 +575,7 @@ namespace Emby.Server.Implementations
{ {
try try
{ {
var assembly = Assembly.LoadFrom(file); var assembly = Assembly.Load(File.ReadAllBytes(file));
return new Tuple<Assembly, string>(assembly, file); return new Tuple<Assembly, string>(assembly, file);
} }
@@ -641,10 +652,8 @@ namespace Emby.Server.Implementations
/// <summary> /// <summary>
/// Runs the startup tasks. /// Runs the startup tasks.
/// </summary> /// </summary>
public async Task RunStartupTasks() public Task RunStartupTasks()
{ {
Logger.LogInformation("Running startup tasks");
Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false)); Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false));
ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated; ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
@@ -663,20 +672,20 @@ namespace Emby.Server.Implementations
Logger.LogInformation("ServerId: {0}", SystemId); Logger.LogInformation("ServerId: {0}", SystemId);
var entryPoints = GetExports<IServerEntryPoint>(); 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"); Logger.LogInformation("Core startup complete");
HttpServer.GlobalResponse = null; HttpServer.GlobalResponse = null;
now = DateTime.UtcNow; Logger.LogInformation("Post-init migrations complete");
await Task.WhenAll(StartEntryPoints(entryPoints, false));
Logger.LogInformation("Executed all post-startup entry points in {Elapsed:fff} ms", DateTime.Now - now); RunEntryPoints(entryPoints, false);
Logger.LogInformation("All entry points have started");
return Task.CompletedTask;
} }
private IEnumerable<Task> StartEntryPoints(IEnumerable<IServerEntryPoint> entryPoints, bool isBeforeStartup) private void RunEntryPoints(IEnumerable<IServerEntryPoint> entryPoints, bool isBeforeStartup)
{ {
foreach (var entryPoint in entryPoints) foreach (var entryPoint in entryPoints)
{ {
@@ -685,13 +694,22 @@ namespace Emby.Server.Implementations
continue; continue;
} }
Logger.LogDebug("Starting entry point {Type}", entryPoint.GetType()); var name = entryPoint.GetType().FullName;
Logger.LogInformation("Starting entry point {Name}", name);
yield return entryPoint.RunAsync(); 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");
} }
} }
public async Task Init() public void Init()
{ {
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
@@ -721,7 +739,7 @@ namespace Emby.Server.Implementations
SetHttpLimit(); SetHttpLimit();
await RegisterResources(); RegisterResources();
FindParts(); FindParts();
} }
@@ -736,7 +754,7 @@ namespace Emby.Server.Implementations
/// <summary> /// <summary>
/// Registers resources that classes will depend on /// Registers resources that classes will depend on
/// </summary> /// </summary>
protected async Task RegisterResources() protected void RegisterResources()
{ {
RegisterSingleInstance(ConfigurationManager); RegisterSingleInstance(ConfigurationManager);
RegisterSingleInstance<IApplicationHost>(this); RegisterSingleInstance<IApplicationHost>(this);
@@ -744,6 +762,7 @@ namespace Emby.Server.Implementations
RegisterSingleInstance<IApplicationPaths>(ApplicationPaths); RegisterSingleInstance<IApplicationPaths>(ApplicationPaths);
RegisterSingleInstance(JsonSerializer); RegisterSingleInstance(JsonSerializer);
RegisterSingleInstance(SystemEvents);
RegisterSingleInstance(LoggerFactory, false); RegisterSingleInstance(LoggerFactory, false);
RegisterSingleInstance(Logger); RegisterSingleInstance(Logger);
@@ -760,7 +779,7 @@ namespace Emby.Server.Implementations
IsoManager = new IsoManager(); IsoManager = new IsoManager();
RegisterSingleInstance(IsoManager); RegisterSingleInstance(IsoManager);
TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager); TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager, SystemEvents);
RegisterSingleInstance(TaskManager); RegisterSingleInstance(TaskManager);
RegisterSingleInstance(XmlSerializer); RegisterSingleInstance(XmlSerializer);
@@ -768,6 +787,9 @@ namespace Emby.Server.Implementations
ProcessFactory = new ProcessFactory(); ProcessFactory = new ProcessFactory();
RegisterSingleInstance(ProcessFactory); RegisterSingleInstance(ProcessFactory);
TimerFactory = new TimerFactory();
RegisterSingleInstance(TimerFactory);
var streamHelper = CreateStreamHelper(); var streamHelper = CreateStreamHelper();
ApplicationHost.StreamHelper = streamHelper; ApplicationHost.StreamHelper = streamHelper;
RegisterSingleInstance(streamHelper); RegisterSingleInstance(streamHelper);
@@ -777,12 +799,12 @@ namespace Emby.Server.Implementations
SocketFactory = new SocketFactory(); SocketFactory = new SocketFactory();
RegisterSingleInstance(SocketFactory); RegisterSingleInstance(SocketFactory);
InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, PackageRuntime);
RegisterSingleInstance(InstallationManager);
ZipClient = new ZipClient(FileSystemManager); ZipClient = new ZipClient(FileSystemManager);
RegisterSingleInstance(ZipClient); RegisterSingleInstance(ZipClient);
InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, ZipClient, PackageRuntime);
RegisterSingleInstance(InstallationManager);
HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor()); HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor());
RegisterSingleInstance(HttpResultFactory); RegisterSingleInstance(HttpResultFactory);
@@ -794,9 +816,9 @@ namespace Emby.Server.Implementations
IAssemblyInfo assemblyInfo = new AssemblyInfo(); IAssemblyInfo assemblyInfo = new AssemblyInfo();
RegisterSingleInstance(assemblyInfo); RegisterSingleInstance(assemblyInfo);
LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory); LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory, assemblyInfo, new TextLocalizer());
await LocalizationManager.LoadAll(); StringExtensions.LocalizationManager = LocalizationManager;
RegisterSingleInstance<ILocalizationManager>(LocalizationManager); RegisterSingleInstance(LocalizationManager);
BlurayExaminer = new BdInfoExaminer(FileSystemManager); BlurayExaminer = new BdInfoExaminer(FileSystemManager);
RegisterSingleInstance(BlurayExaminer); RegisterSingleInstance(BlurayExaminer);
@@ -814,8 +836,9 @@ namespace Emby.Server.Implementations
DisplayPreferencesRepository = displayPreferencesRepo; DisplayPreferencesRepository = displayPreferencesRepo;
RegisterSingleInstance(DisplayPreferencesRepository); RegisterSingleInstance(DisplayPreferencesRepository);
ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo); var itemRepo = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo, FileSystemManager, EnvironmentInfo, TimerFactory);
RegisterSingleInstance<IItemRepository>(ItemRepository); ItemRepository = itemRepo;
RegisterSingleInstance(ItemRepository);
AuthenticationRepository = GetAuthenticationRepository(); AuthenticationRepository = GetAuthenticationRepository();
RegisterSingleInstance(AuthenticationRepository); RegisterSingleInstance(AuthenticationRepository);
@@ -830,7 +853,7 @@ namespace Emby.Server.Implementations
var musicManager = new MusicManager(LibraryManager); var musicManager = new MusicManager(LibraryManager);
RegisterSingleInstance<IMusicManager>(new MusicManager(LibraryManager)); RegisterSingleInstance<IMusicManager>(new MusicManager(LibraryManager));
LibraryMonitor = new LibraryMonitor(LoggerFactory, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo); LibraryMonitor = new LibraryMonitor(LoggerFactory, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, TimerFactory, SystemEvents, EnvironmentInfo);
RegisterSingleInstance(LibraryMonitor); RegisterSingleInstance(LibraryMonitor);
RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LoggerFactory, LibraryManager, UserManager)); RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LoggerFactory, LibraryManager, UserManager));
@@ -862,10 +885,10 @@ namespace Emby.Server.Implementations
DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LoggerFactory, NetworkManager); DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LoggerFactory, NetworkManager);
RegisterSingleInstance(DeviceManager); RegisterSingleInstance(DeviceManager);
MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder); MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, TimerFactory, () => MediaEncoder);
RegisterSingleInstance(MediaSourceManager); RegisterSingleInstance(MediaSourceManager);
SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager); SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, ServerConfigurationManager, LocalizationManager);
RegisterSingleInstance(SubtitleManager); RegisterSingleInstance(SubtitleManager);
ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, LoggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer); ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, LoggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer);
@@ -877,7 +900,7 @@ namespace Emby.Server.Implementations
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager); ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager);
RegisterSingleInstance(ChannelManager); RegisterSingleInstance(ChannelManager);
SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager); SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager, TimerFactory);
RegisterSingleInstance(SessionManager); RegisterSingleInstance(SessionManager);
var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo); var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo);
@@ -889,7 +912,7 @@ namespace Emby.Server.Implementations
PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager); PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager);
RegisterSingleInstance(PlaylistManager); RegisterSingleInstance(PlaylistManager);
LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager); LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, ProviderManager, FileSystemManager, () => ChannelManager);
RegisterSingleInstance(LiveTvManager); RegisterSingleInstance(LiveTvManager);
UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager); UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
@@ -898,7 +921,7 @@ namespace Emby.Server.Implementations
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager); NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
RegisterSingleInstance(NotificationManager); RegisterSingleInstance(NotificationManager);
RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory)); RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory, TimerFactory));
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
RegisterSingleInstance(ChapterManager); RegisterSingleInstance(ChapterManager);
@@ -933,7 +956,7 @@ namespace Emby.Server.Implementations
((UserManager)UserManager).Initialize(); ((UserManager)UserManager).Initialize();
((UserDataManager)UserDataManager).Repository = userDataRepo; ((UserDataManager)UserDataManager).Repository = userDataRepo;
ItemRepository.Initialize(userDataRepo, UserManager); itemRepo.Initialize(userDataRepo, UserManager);
((LibraryManager)LibraryManager).ItemRepository = ItemRepository; ((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
} }
@@ -992,7 +1015,7 @@ namespace Emby.Server.Implementations
try try
{ {
if (!File.Exists(certificateLocation)) if (!FileSystemManager.FileExists(certificateLocation))
{ {
return null; return null;
} }
@@ -1418,7 +1441,7 @@ namespace Emby.Server.Implementations
//if (generateCertificate) //if (generateCertificate)
//{ //{
// if (!File.Exists(certPath)) // if (!FileSystemManager.FileExists(certPath))
// { // {
// FileSystemManager.CreateDirectory(FileSystemManager.GetDirectoryName(certPath)); // FileSystemManager.CreateDirectory(FileSystemManager.GetDirectoryName(certPath));
@@ -1548,7 +1571,7 @@ namespace Emby.Server.Implementations
/// <returns>IEnumerable{Assembly}.</returns> /// <returns>IEnumerable{Assembly}.</returns>
protected List<Tuple<Assembly, string>> GetComposablePartAssemblies() protected List<Tuple<Assembly, string>> GetComposablePartAssemblies()
{ {
var list = GetPluginAssemblies(ApplicationPaths.PluginsPath); var list = GetPluginAssemblies();
// Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that // 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 // This will prevent the .dll file from getting locked, and allow us to replace it when needed
@@ -1599,11 +1622,84 @@ namespace Emby.Server.Implementations
protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal(); 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) private List<Tuple<Assembly, string>> GetPluginAssemblies(string path)
{ {
try try
{ {
return FilterAssembliesToLoad(Directory.EnumerateFiles(path, "*.dll", SearchOption.AllDirectories)) return FilterAssembliesToLoad(Directory.EnumerateFiles(path, "*.dll", SearchOption.TopDirectoryOnly))
.Select(LoadAssembly) .Select(LoadAssembly)
.Where(a => a != null) .Where(a => a != null)
.ToList(); .ToList();
@@ -1656,6 +1752,7 @@ namespace Emby.Server.Implementations
var minRequiredVersions = new Dictionary<string, Version>(StringComparer.OrdinalIgnoreCase) var minRequiredVersions = new Dictionary<string, Version>(StringComparer.OrdinalIgnoreCase)
{ {
{ "GameBrowser.dll", new Version(3, 1) },
{ "moviethemesongs.dll", new Version(1, 6) }, { "moviethemesongs.dll", new Version(1, 6) },
{ "themesongs.dll", new Version(1, 2) } { "themesongs.dll", new Version(1, 2) }
}; };
@@ -1731,7 +1828,7 @@ namespace Emby.Server.Implementations
EncoderLocationType = MediaEncoder.EncoderLocationType, EncoderLocationType = MediaEncoder.EncoderLocationType,
SystemArchitecture = EnvironmentInfo.SystemArchitecture, SystemArchitecture = EnvironmentInfo.SystemArchitecture,
SystemUpdateLevel = SystemUpdateLevel, SystemUpdateLevel = SystemUpdateLevel,
PackageName = StartupOptions.PackageName PackageName = StartupOptions.GetOption("-package")
}; };
} }
@@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param> /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles) public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
{ {
using (var fileStream = File.OpenRead(sourceFile)) using (var fileStream = _fileSystem.OpenRead(sourceFile))
{ {
ExtractAll(fileStream, targetPath, overwriteExistingFiles); 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> /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles) public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
{ {
using (var fileStream = File.OpenRead(sourceFile)) using (var fileStream = _fileSystem.OpenRead(sourceFile))
{ {
ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles); 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> /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles) public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
{ {
using (var fileStream = File.OpenRead(sourceFile)) using (var fileStream = _fileSystem.OpenRead(sourceFile))
{ {
ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles); ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
} }
@@ -355,7 +355,7 @@ namespace Emby.Server.Implementations.Channels
return; return;
} }
Directory.CreateDirectory(Path.GetDirectoryName(path)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
_jsonSerializer.SerializeToFile(mediaSources, path); _jsonSerializer.SerializeToFile(mediaSources, path);
} }
@@ -681,17 +681,22 @@ namespace Emby.Server.Implementations.Channels
// Find the corresponding channel provider plugin // Find the corresponding channel provider plugin
var channelProvider = GetChannelProvider(channel); var channelProvider = GetChannelProvider(channel);
var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId); var user = query.User;
ChannelItemSortField? sortField = null;
var sortDescending = false;
var parentItem = !query.ParentId.Equals(Guid.Empty) ? _libraryManager.GetItemById(query.ParentId) : channel;
var itemsResult = await GetChannelItems(channelProvider, var itemsResult = await GetChannelItems(channelProvider,
query.User, user,
parentItem is Channel ? null : parentItem.ExternalId, parentItem is Channel ? null : parentItem.ExternalId,
null, sortField,
false, sortDescending,
cancellationToken) cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
if (query.ParentId == Guid.Empty) if (query.ParentId.Equals(Guid.Empty))
{ {
query.Parent = channel; query.Parent = channel;
} }
@@ -834,7 +839,7 @@ namespace Emby.Server.Implementations.Channels
{ {
try try
{ {
Directory.CreateDirectory(Path.GetDirectoryName(path)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
_jsonSerializer.SerializeToFile(result, path); _jsonSerializer.SerializeToFile(result, path);
} }
@@ -70,8 +70,7 @@ namespace Emby.Server.Implementations.Collections
return null; return null;
}) })
.Where(i => i != null) .Where(i => i != null)
.GroupBy(x => x.Id) .DistinctBy(i => i.Id)
.Select(x => x.First())
.OrderBy(i => Guid.NewGuid()) .OrderBy(i => Guid.NewGuid())
.ToList(); .ToList();
} }
@@ -76,7 +76,7 @@ namespace Emby.Server.Implementations.Collections
return null; return null;
} }
Directory.CreateDirectory(path); _fileSystem.CreateDirectory(path);
var libraryOptions = new LibraryOptions var libraryOptions = new LibraryOptions
{ {
@@ -133,7 +133,7 @@ namespace Emby.Server.Implementations.Collections
try try
{ {
Directory.CreateDirectory(path); _fileSystem.CreateDirectory(path);
var collection = new BoxSet var collection = new BoxSet
{ {
@@ -353,13 +353,13 @@ namespace Emby.Server.Implementations.Collections
_logger = logger; _logger = logger;
} }
public async Task RunAsync() public async void Run()
{ {
if (!_config.Configuration.CollectionsUpgraded && _config.Configuration.IsStartupWizardCompleted) if (!_config.Configuration.CollectionsUpgraded && _config.Configuration.IsStartupWizardCompleted)
{ {
var path = _collectionManager.GetCollectionsFolderPath(); var path = _collectionManager.GetCollectionsFolderPath();
if (Directory.Exists(path)) if (_fileSystem.DirectoryExists(path))
{ {
try try
{ {
@@ -148,7 +148,7 @@ namespace Emby.Server.Implementations.Configuration
&& !string.Equals(Configuration.CertificatePath ?? string.Empty, newPath)) && !string.Equals(Configuration.CertificatePath ?? string.Empty, newPath))
{ {
// Validate // Validate
if (!File.Exists(newPath)) if (!FileSystem.FileExists(newPath))
{ {
throw new FileNotFoundException(string.Format("Certificate file '{0}' does not exist.", 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)) && !string.Equals(Configuration.MetadataPath ?? string.Empty, newPath))
{ {
// Validate // Validate
if (!Directory.Exists(newPath)) if (!FileSystem.DirectoryExists(newPath))
{ {
throw new FileNotFoundException(string.Format("{0} does not exist.", 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; var path = CachePath;
Directory.CreateDirectory(Path.GetDirectoryName(path)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
lock (_syncLock) lock (_syncLock)
{ {
File.WriteAllText(path, id, Encoding.UTF8); _fileSystem.WriteAllText(path, id, Encoding.UTF8);
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -76,7 +76,7 @@ namespace Emby.Server.Implementations.Devices
public void SaveCapabilities(string deviceId, ClientCapabilities capabilities) public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
{ {
var path = Path.Combine(GetDevicePath(deviceId), "capabilities.json"); var path = Path.Combine(GetDevicePath(deviceId), "capabilities.json");
Directory.CreateDirectory(Path.GetDirectoryName(path)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
lock (_capabilitiesSyncLock) lock (_capabilitiesSyncLock)
{ {
@@ -239,7 +239,7 @@ namespace Emby.Server.Implementations.Devices
path = Path.Combine(path, file.Name); path = Path.Combine(path, file.Name);
path = Path.ChangeExtension(path, MimeTypes.ToExtension(file.MimeType) ?? "jpg"); path = Path.ChangeExtension(path, MimeTypes.ToExtension(file.MimeType) ?? "jpg");
Directory.CreateDirectory(Path.GetDirectoryName(path)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
await EnsureLibraryFolder(uploadPathInfo.Item2, uploadPathInfo.Item3).ConfigureAwait(false); await EnsureLibraryFolder(uploadPathInfo.Item2, uploadPathInfo.Item3).ConfigureAwait(false);
@@ -275,7 +275,7 @@ namespace Emby.Server.Implementations.Devices
private void AddCameraUpload(string deviceId, LocalFileInfo file) private void AddCameraUpload(string deviceId, LocalFileInfo file)
{ {
var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
Directory.CreateDirectory(Path.GetDirectoryName(path)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
lock (_cameraUploadSyncLock) lock (_cameraUploadSyncLock)
{ {
@@ -317,7 +317,7 @@ namespace Emby.Server.Implementations.Devices
return Task.CompletedTask; return Task.CompletedTask;
} }
Directory.CreateDirectory(path); _fileSystem.CreateDirectory(path);
var libraryOptions = new LibraryOptions var libraryOptions = new LibraryOptions
{ {
@@ -425,13 +425,13 @@ namespace Emby.Server.Implementations.Devices
_logger = logger; _logger = logger;
} }
public async Task RunAsync() public async void Run()
{ {
if (!_config.Configuration.CameraUploadUpgraded && _config.Configuration.IsStartupWizardCompleted) if (!_config.Configuration.CameraUploadUpgraded && _config.Configuration.IsStartupWizardCompleted)
{ {
var path = _deviceManager.GetUploadsPath(); var path = _deviceManager.GetUploadsPath();
if (Directory.Exists(path)) if (_fileSystem.DirectoryExists(path))
{ {
try try
{ {
@@ -108,19 +108,23 @@ namespace Emby.Server.Implementations.Diagnostics
public Task<bool> WaitForExitAsync(int timeMs) public Task<bool> WaitForExitAsync(int timeMs)
{ {
//Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true. //if (_process.WaitForExit(100))
//{
if (HasExited) // return Task.FromResult(true);
{ //}
return Task.FromResult(true);
}
//timeMs -= 100;
timeMs = Math.Max(0, timeMs); timeMs = Math.Max(0, timeMs);
var tcs = new TaskCompletionSource<bool>(); var tcs = new TaskCompletionSource<bool>();
var cancellationToken = new CancellationTokenSource(timeMs).Token; var cancellationToken = new CancellationTokenSource(timeMs).Token;
if (HasExited)
{
return Task.FromResult(true);
}
_process.Exited += (sender, args) => tcs.TrySetResult(true); _process.Exited += (sender, args) => tcs.TrySetResult(true);
cancellationToken.Register(() => tcs.TrySetResult(HasExited)); cancellationToken.Register(() => tcs.TrySetResult(HasExited));
+42 -8
View File
@@ -374,6 +374,10 @@ namespace Emby.Server.Implementations.Dto
dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
dto.SongCount = taggedItems.Count(i => i is Audio); dto.SongCount = taggedItems.Count(i => i is Audio);
} }
else if (item is GameGenre)
{
dto.GameCount = taggedItems.Count(i => i is Game);
}
else else
{ {
// This populates them all and covers Genre, Person, Studio, Year // This populates them all and covers Genre, Person, Studio, Year
@@ -381,6 +385,7 @@ namespace Emby.Server.Implementations.Dto
dto.ArtistCount = taggedItems.Count(i => i is MusicArtist); dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum); dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
dto.EpisodeCount = taggedItems.Count(i => i is Episode); 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.MovieCount = taggedItems.Count(i => i is Movie);
dto.TrailerCount = taggedItems.Count(i => i is Trailer); dto.TrailerCount = taggedItems.Count(i => i is Trailer);
dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
@@ -527,6 +532,17 @@ namespace Emby.Server.Implementations.Dto
dto.Album = item.Album; 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) private string[] GetImageTags(BaseItem item, List<ItemImageInfo> images)
{ {
return images return images
@@ -620,8 +636,7 @@ namespace Emby.Server.Implementations.Dto
} }
}).Where(i => i != null) }).Where(i => i != null)
.GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
.Select(x => x.First())
.ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase); .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
for (var i = 0; i < people.Count; i++) for (var i = 0; i < people.Count; i++)
@@ -683,6 +698,11 @@ namespace Emby.Server.Implementations.Dto
return _libraryManager.GetMusicGenreId(name); return _libraryManager.GetMusicGenreId(name);
} }
if (owner is Game || owner is GameSystem)
{
return _libraryManager.GetGameGenreId(name);
}
return _libraryManager.GetGenreId(name); return _libraryManager.GetGenreId(name);
} }
@@ -1186,6 +1206,20 @@ 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; var musicVideo = item as MusicVideo;
if (musicVideo != null) if (musicVideo != null)
{ {
@@ -1394,7 +1428,7 @@ namespace Emby.Server.Implementations.Dto
var supportedEnhancers = _imageProcessor.GetSupportedEnhancers(item, ImageType.Primary); var supportedEnhancers = _imageProcessor.GetSupportedEnhancers(item, ImageType.Primary);
ImageDimensions size; ImageSize size;
var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio(); var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
@@ -1405,9 +1439,9 @@ namespace Emby.Server.Implementations.Dto
return defaultAspectRatio; return defaultAspectRatio;
} }
int dummyWidth = 200; double dummyWidth = 200;
int dummyHeight = Convert.ToInt32(dummyWidth / defaultAspectRatio); double dummyHeight = dummyWidth / defaultAspectRatio;
size = new ImageDimensions(dummyWidth, dummyHeight); size = new ImageSize(dummyWidth, dummyHeight);
} }
else else
{ {
@@ -1418,7 +1452,7 @@ namespace Emby.Server.Implementations.Dto
try try
{ {
size = _imageProcessor.GetImageDimensions(item, imageInfo); size = _imageProcessor.GetImageSize(item, imageInfo);
if (size.Width <= 0 || size.Height <= 0) if (size.Width <= 0 || size.Height <= 0)
{ {
@@ -1447,7 +1481,7 @@ namespace Emby.Server.Implementations.Dto
var width = size.Width; var width = size.Width;
var height = size.Height; var height = size.Height;
if (width <= 0 || height <= 0) if (width.Equals(0) || height.Equals(0))
{ {
return null; return null;
} }
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" /> <ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
@@ -25,7 +25,8 @@
<PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" /> <PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" />
<PackageReference Include="sharpcompress" Version="0.22.0" /> <PackageReference Include="sharpcompress" Version="0.22.0" />
<PackageReference Include="SimpleInjector" Version="4.4.2" /> <PackageReference Include="SimpleInjector" Version="4.4.2" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="1.0.0" /> <PackageReference Include="SQLitePCL.pretty.core" Version="1.1.8" />
<PackageReference Include="SQLitePCLRaw.core" Version="1.1.11" />
<PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" /> <PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" />
</ItemGroup> </ItemGroup>
@@ -42,7 +43,7 @@
<EmbeddedResource Include="Localization\iso6392.txt" /> <EmbeddedResource Include="Localization\iso6392.txt" />
<EmbeddedResource Include="Localization\countries.json" /> <EmbeddedResource Include="Localization\countries.json" />
<EmbeddedResource Include="Localization\Core\*.json" /> <EmbeddedResource Include="Localization\Core\*.json" />
<EmbeddedResource Include="Localization\Ratings\*.csv" /> <EmbeddedResource Include="Localization\Ratings\*.txt" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -9,6 +9,7 @@ using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints namespace Emby.Server.Implementations.EntryPoints
@@ -21,10 +22,11 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly ILiveTvManager _liveTvManager; private readonly ILiveTvManager _liveTvManager;
private readonly ITimerFactory _timerFactory;
private Timer _timer; private ITimer _timer;
public AutomaticRestartEntryPoint(IServerApplicationHost appHost, ILogger logger, ITaskManager iTaskManager, ISessionManager sessionManager, IServerConfigurationManager config, ILiveTvManager liveTvManager) public AutomaticRestartEntryPoint(IServerApplicationHost appHost, ILogger logger, ITaskManager iTaskManager, ISessionManager sessionManager, IServerConfigurationManager config, ILiveTvManager liveTvManager, ITimerFactory timerFactory)
{ {
_appHost = appHost; _appHost = appHost;
_logger = logger; _logger = logger;
@@ -32,16 +34,15 @@ namespace Emby.Server.Implementations.EntryPoints
_sessionManager = sessionManager; _sessionManager = sessionManager;
_config = config; _config = config;
_liveTvManager = liveTvManager; _liveTvManager = liveTvManager;
_timerFactory = timerFactory;
} }
public Task RunAsync() public void Run()
{ {
if (_appHost.CanSelfRestart) if (_appHost.CanSelfRestart)
{ {
_appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged; _appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged;
} }
return Task.CompletedTask;
} }
void _appHost_HasPendingRestartChanged(object sender, EventArgs e) void _appHost_HasPendingRestartChanged(object sender, EventArgs e)
@@ -50,7 +51,7 @@ namespace Emby.Server.Implementations.EntryPoints
if (_appHost.HasPendingRestart) if (_appHost.HasPendingRestart)
{ {
_timer = new Timer(TimerCallback, null, TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(15)); _timer = _timerFactory.Create(TimerCallback, null, TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(15));
} }
} }
@@ -10,6 +10,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Mono.Nat; using Mono.Nat;
@@ -23,17 +24,19 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IDeviceDiscovery _deviceDiscovery; private readonly IDeviceDiscovery _deviceDiscovery;
private Timer _timer; private ITimer _timer;
private readonly ITimerFactory _timerFactory;
private NatManager _natManager; private NatManager _natManager;
public ExternalPortForwarding(ILoggerFactory loggerFactory, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient) public ExternalPortForwarding(ILoggerFactory loggerFactory, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, ITimerFactory timerFactory)
{ {
_logger = loggerFactory.CreateLogger("PortMapper"); _logger = loggerFactory.CreateLogger("PortMapper");
_appHost = appHost; _appHost = appHost;
_config = config; _config = config;
_deviceDiscovery = deviceDiscovery; _deviceDiscovery = deviceDiscovery;
_httpClient = httpClient; _httpClient = httpClient;
_timerFactory = timerFactory;
_config.ConfigurationUpdated += _config_ConfigurationUpdated1; _config.ConfigurationUpdated += _config_ConfigurationUpdated1;
} }
@@ -58,17 +61,17 @@ namespace Emby.Server.Implementations.EntryPoints
return string.Join("|", values.ToArray()); return string.Join("|", values.ToArray());
} }
private async void _config_ConfigurationUpdated(object sender, EventArgs e) void _config_ConfigurationUpdated(object sender, EventArgs e)
{ {
if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase)) if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
{ {
DisposeNat(); DisposeNat();
await RunAsync(); Run();
} }
} }
public Task RunAsync() public void Run()
{ {
if (_config.Configuration.EnableUPnP && _config.Configuration.EnableRemoteAccess) if (_config.Configuration.EnableUPnP && _config.Configuration.EnableRemoteAccess)
{ {
@@ -77,8 +80,6 @@ namespace Emby.Server.Implementations.EntryPoints
_config.ConfigurationUpdated -= _config_ConfigurationUpdated; _config.ConfigurationUpdated -= _config_ConfigurationUpdated;
_config.ConfigurationUpdated += _config_ConfigurationUpdated; _config.ConfigurationUpdated += _config_ConfigurationUpdated;
return Task.CompletedTask;
} }
private void Start() private void Start()
@@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.EntryPoints
_natManager.StartDiscovery(); _natManager.StartDiscovery();
} }
_timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); _timer = _timerFactory.Create(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@@ -14,6 +13,7 @@ using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints namespace Emby.Server.Implementations.EntryPoints
@@ -28,6 +28,7 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ITimerFactory _timerFactory;
/// <summary> /// <summary>
/// The _library changed sync lock /// The _library changed sync lock
@@ -45,7 +46,7 @@ namespace Emby.Server.Implementations.EntryPoints
/// Gets or sets the library update timer. /// Gets or sets the library update timer.
/// </summary> /// </summary>
/// <value>The library update timer.</value> /// <value>The library update timer.</value>
private Timer LibraryUpdateTimer { get; set; } private ITimer LibraryUpdateTimer { get; set; }
/// <summary> /// <summary>
/// The library update duration /// The library update duration
@@ -54,16 +55,17 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly IProviderManager _providerManager; private readonly IProviderManager _providerManager;
public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger, IProviderManager providerManager) public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger, ITimerFactory timerFactory, IProviderManager providerManager)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_sessionManager = sessionManager; _sessionManager = sessionManager;
_userManager = userManager; _userManager = userManager;
_logger = logger; _logger = logger;
_timerFactory = timerFactory;
_providerManager = providerManager; _providerManager = providerManager;
} }
public Task RunAsync() public void Run()
{ {
_libraryManager.ItemAdded += libraryManager_ItemAdded; _libraryManager.ItemAdded += libraryManager_ItemAdded;
_libraryManager.ItemUpdated += libraryManager_ItemUpdated; _libraryManager.ItemUpdated += libraryManager_ItemUpdated;
@@ -72,8 +74,6 @@ namespace Emby.Server.Implementations.EntryPoints
_providerManager.RefreshCompleted += _providerManager_RefreshCompleted; _providerManager.RefreshCompleted += _providerManager_RefreshCompleted;
_providerManager.RefreshStarted += _providerManager_RefreshStarted; _providerManager.RefreshStarted += _providerManager_RefreshStarted;
_providerManager.RefreshProgress += _providerManager_RefreshProgress; _providerManager.RefreshProgress += _providerManager_RefreshProgress;
return Task.CompletedTask;
} }
private Dictionary<Guid, DateTime> _lastProgressMessageTimes = new Dictionary<Guid, DateTime>(); private Dictionary<Guid, DateTime> _lastProgressMessageTimes = new Dictionary<Guid, DateTime>();
@@ -188,7 +188,7 @@ namespace Emby.Server.Implementations.EntryPoints
{ {
if (LibraryUpdateTimer == null) if (LibraryUpdateTimer == null)
{ {
LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
Timeout.Infinite); Timeout.Infinite);
} }
else else
@@ -222,7 +222,7 @@ namespace Emby.Server.Implementations.EntryPoints
{ {
if (LibraryUpdateTimer == null) if (LibraryUpdateTimer == null)
{ {
LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
Timeout.Infinite); Timeout.Infinite);
} }
else else
@@ -250,7 +250,7 @@ namespace Emby.Server.Implementations.EntryPoints
{ {
if (LibraryUpdateTimer == null) if (LibraryUpdateTimer == null)
{ {
LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
Timeout.Infinite); Timeout.Infinite);
} }
else else
@@ -277,21 +277,14 @@ namespace Emby.Server.Implementations.EntryPoints
lock (_libraryChangedSyncLock) lock (_libraryChangedSyncLock)
{ {
// Remove dupes in case some were saved multiple times // Remove dupes in case some were saved multiple times
var foldersAddedTo = _foldersAddedTo var foldersAddedTo = _foldersAddedTo.DistinctBy(i => i.Id).ToList();
.GroupBy(x => x.Id)
.Select(x => x.First())
.ToList();
var foldersRemovedFrom = _foldersRemovedFrom var foldersRemovedFrom = _foldersRemovedFrom.DistinctBy(i => i.Id).ToList();
.GroupBy(x => x.Id)
.Select(x => x.First())
.ToList();
var itemsUpdated = _itemsUpdated var itemsUpdated = _itemsUpdated
.Where(i => !_itemsAdded.Contains(i)) .Where(i => !_itemsAdded.Contains(i))
.GroupBy(x => x.Id) .DistinctBy(i => i.Id)
.Select(x => x.First()) .ToList();
.ToList();
SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None); SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None);
@@ -1,7 +1,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Plugins;
@@ -25,14 +24,12 @@ namespace Emby.Server.Implementations.EntryPoints
_liveTvManager = liveTvManager; _liveTvManager = liveTvManager;
} }
public Task RunAsync() public void Run()
{ {
_liveTvManager.TimerCancelled += _liveTvManager_TimerCancelled; _liveTvManager.TimerCancelled += _liveTvManager_TimerCancelled;
_liveTvManager.SeriesTimerCancelled += _liveTvManager_SeriesTimerCancelled; _liveTvManager.SeriesTimerCancelled += _liveTvManager_SeriesTimerCancelled;
_liveTvManager.TimerCreated += _liveTvManager_TimerCreated; _liveTvManager.TimerCreated += _liveTvManager_TimerCreated;
_liveTvManager.SeriesTimerCreated += _liveTvManager_SeriesTimerCreated; _liveTvManager.SeriesTimerCreated += _liveTvManager_SeriesTimerCreated;
return Task.CompletedTask;
} }
private void _liveTvManager_SeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) private void _liveTvManager_SeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
@@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates; using MediaBrowser.Common.Updates;
using MediaBrowser.Controller; using MediaBrowser.Controller;
@@ -50,7 +49,7 @@ namespace Emby.Server.Implementations.EntryPoints
_sessionManager = sessionManager; _sessionManager = sessionManager;
} }
public Task RunAsync() public void Run()
{ {
_userManager.UserDeleted += userManager_UserDeleted; _userManager.UserDeleted += userManager_UserDeleted;
_userManager.UserUpdated += userManager_UserUpdated; _userManager.UserUpdated += userManager_UserUpdated;
@@ -66,8 +65,6 @@ namespace Emby.Server.Implementations.EntryPoints
_installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed; _installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed;
_taskManager.TaskCompleted += _taskManager_TaskCompleted; _taskManager.TaskCompleted += _taskManager_TaskCompleted;
return Task.CompletedTask;
} }
void _installationManager_PackageInstalling(object sender, InstallationEventArgs e) void _installationManager_PackageInstalling(object sender, InstallationEventArgs e)
@@ -1,4 +1,3 @@
using System.Threading.Tasks;
using Emby.Server.Implementations.Browser; using Emby.Server.Implementations.Browser;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@@ -33,11 +32,11 @@ namespace Emby.Server.Implementations.EntryPoints
/// <summary> /// <summary>
/// Runs this instance. /// Runs this instance.
/// </summary> /// </summary>
public Task RunAsync() public void Run()
{ {
if (!_appHost.CanLaunchWebBrowser) if (!_appHost.CanLaunchWebBrowser)
{ {
return Task.CompletedTask; return;
} }
if (!_config.Configuration.IsStartupWizardCompleted) if (!_config.Configuration.IsStartupWizardCompleted)
@@ -48,13 +47,11 @@ namespace Emby.Server.Implementations.EntryPoints
{ {
var options = ((ApplicationHost)_appHost).StartupOptions; var options = ((ApplicationHost)_appHost).StartupOptions;
if (!options.NoAutoRunWebApp) if (!options.ContainsOption("-noautorunwebapp"))
{ {
BrowserLauncher.OpenWebApp(_appHost); BrowserLauncher.OpenWebApp(_appHost);
} }
} }
return Task.CompletedTask;
} }
/// <summary> /// <summary>
@@ -0,0 +1,34 @@
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,5 +1,4 @@
using System; using System;
using System.Threading.Tasks;
using Emby.Server.Implementations.Udp; using Emby.Server.Implementations.Udp;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Plugins;
@@ -44,7 +43,7 @@ namespace Emby.Server.Implementations.EntryPoints
/// <summary> /// <summary>
/// Runs this instance. /// Runs this instance.
/// </summary> /// </summary>
public Task RunAsync() public void Run()
{ {
var udpServer = new UdpServer(_logger, _appHost, _json, _socketFactory); var udpServer = new UdpServer(_logger, _appHost, _json, _socketFactory);
@@ -58,8 +57,6 @@ namespace Emby.Server.Implementations.EntryPoints
{ {
_logger.LogError(ex, "Failed to start UDP Server"); _logger.LogError(ex, "Failed to start UDP Server");
} }
return Task.CompletedTask;
} }
/// <summary> /// <summary>
@@ -10,6 +10,7 @@ using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints namespace Emby.Server.Implementations.EntryPoints
@@ -22,24 +23,24 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly object _syncLock = new object(); private readonly object _syncLock = new object();
private Timer UpdateTimer { get; set; } private ITimer UpdateTimer { get; set; }
private readonly ITimerFactory _timerFactory;
private const int UpdateDuration = 500; private const int UpdateDuration = 500;
private readonly Dictionary<Guid, List<BaseItem>> _changedItems = new Dictionary<Guid, List<BaseItem>>(); private readonly Dictionary<Guid, List<BaseItem>> _changedItems = new Dictionary<Guid, List<BaseItem>>();
public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager) public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager, ITimerFactory timerFactory)
{ {
_userDataManager = userDataManager; _userDataManager = userDataManager;
_sessionManager = sessionManager; _sessionManager = sessionManager;
_logger = logger; _logger = logger;
_userManager = userManager; _userManager = userManager;
_timerFactory = timerFactory;
} }
public Task RunAsync() public void Run()
{ {
_userDataManager.UserDataSaved += _userDataManager_UserDataSaved; _userDataManager.UserDataSaved += _userDataManager_UserDataSaved;
return Task.CompletedTask;
} }
void _userDataManager_UserDataSaved(object sender, UserDataSaveEventArgs e) void _userDataManager_UserDataSaved(object sender, UserDataSaveEventArgs e)
@@ -53,7 +54,7 @@ namespace Emby.Server.Implementations.EntryPoints
{ {
if (UpdateTimer == null) if (UpdateTimer == null)
{ {
UpdateTimer = new Timer(UpdateTimerCallback, null, UpdateDuration, UpdateTimer = _timerFactory.Create(UpdateTimerCallback, null, UpdateDuration,
Timeout.Infinite); Timeout.Infinite);
} }
else else
@@ -120,8 +121,7 @@ namespace Emby.Server.Implementations.EntryPoints
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
var dtoList = changedItems var dtoList = changedItems
.GroupBy(x => x.Id) .DistinctBy(i => i.Id)
.Select(x => x.First())
.Select(i => .Select(i =>
{ {
var dto = _userDataManager.GetUserDataDto(i, user); var dto = _userDataManager.GetUserDataDto(i, user);
@@ -28,10 +28,10 @@ namespace Emby.Server.Implementations.FFMpeg
_ffmpegInstallInfo = ffmpegInstallInfo; _ffmpegInstallInfo = ffmpegInstallInfo;
} }
public FFMpegInfo GetFFMpegInfo(IStartupOptions options) public FFMpegInfo GetFFMpegInfo(StartupOptions options)
{ {
var customffMpegPath = options.FFmpegPath; var customffMpegPath = options.GetOption("-ffmpeg");
var customffProbePath = options.FFprobePath; var customffProbePath = options.GetOption("-ffprobe");
if (!string.IsNullOrWhiteSpace(customffMpegPath) && !string.IsNullOrWhiteSpace(customffProbePath)) if (!string.IsNullOrWhiteSpace(customffMpegPath) && !string.IsNullOrWhiteSpace(customffProbePath))
{ {
@@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.FFMpeg
var prebuiltFolder = _appPaths.ProgramSystemPath; var prebuiltFolder = _appPaths.ProgramSystemPath;
var prebuiltffmpeg = Path.Combine(prebuiltFolder, downloadInfo.FFMpegFilename); var prebuiltffmpeg = Path.Combine(prebuiltFolder, downloadInfo.FFMpegFilename);
var prebuiltffprobe = Path.Combine(prebuiltFolder, downloadInfo.FFProbeFilename); var prebuiltffprobe = Path.Combine(prebuiltFolder, downloadInfo.FFProbeFilename);
if (File.Exists(prebuiltffmpeg) && File.Exists(prebuiltffprobe)) if (_fileSystem.FileExists(prebuiltffmpeg) && _fileSystem.FileExists(prebuiltffprobe))
{ {
return new FFMpegInfo return new FFMpegInfo
{ {
@@ -75,11 +75,11 @@ namespace Emby.Server.Implementations.FFMpeg
Version = version Version = version
}; };
Directory.CreateDirectory(versionedDirectoryPath); _fileSystem.CreateDirectory(versionedDirectoryPath);
var excludeFromDeletions = new List<string> { versionedDirectoryPath }; var excludeFromDeletions = new List<string> { versionedDirectoryPath };
if (!File.Exists(info.ProbePath) || !File.Exists(info.EncoderPath)) if (!_fileSystem.FileExists(info.ProbePath) || !_fileSystem.FileExists(info.EncoderPath))
{ {
// ffmpeg not present. See if there's an older version we can start with // ffmpeg not present. See if there's an older version we can start with
var existingVersion = GetExistingVersion(info, rootEncoderPath); var existingVersion = GetExistingVersion(info, rootEncoderPath);
@@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.FFMpeg
else else
{ {
info = existingVersion; info = existingVersion;
versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath); versionedDirectoryPath = _fileSystem.GetDirectoryName(info.EncoderPath);
excludeFromDeletions.Add(versionedDirectoryPath); excludeFromDeletions.Add(versionedDirectoryPath);
} }
} }
@@ -130,7 +130,7 @@ namespace Emby.Server.Implementations.FFMpeg
{ {
EncoderPath = encoder, EncoderPath = encoder,
ProbePath = probe, ProbePath = probe,
Version = Path.GetFileName(Path.GetDirectoryName(probe)) Version = Path.GetFileName(_fileSystem.GetDirectoryName(probe))
}; };
} }
} }
@@ -66,6 +66,11 @@ namespace Emby.Server.Implementations.HttpClientManager
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
ServicePointManager.Expect100Continue = false; ServicePointManager.Expect100Continue = false;
#if NET46
// Trakt requests sometimes fail without this
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
#endif
} }
/// <summary> /// <summary>
@@ -101,6 +106,23 @@ namespace Emby.Server.Implementations.HttpClientManager
return client; 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) private WebRequest GetRequest(HttpRequestOptions options, string method)
{ {
string url = options.Url; string url = options.Url;
@@ -113,7 +135,7 @@ namespace Emby.Server.Implementations.HttpClientManager
url = url.Replace(userInfo + "@", string.Empty); url = url.Replace(userInfo + "@", string.Empty);
} }
var request = WebRequest.Create(url); var request = CreateWebRequest(url);
if (request is HttpWebRequest httpWebRequest) if (request is HttpWebRequest httpWebRequest)
{ {
@@ -242,7 +264,7 @@ namespace Emby.Server.Implementations.HttpClientManager
} }
var url = options.Url; var url = options.Url;
var urlHash = url.ToLowerInvariant().GetMD5().ToString("N"); var urlHash = url.ToLower().GetMD5().ToString("N");
var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash); var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash);
@@ -264,18 +286,28 @@ namespace Emby.Server.Implementations.HttpClientManager
private HttpResponseInfo GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url) private HttpResponseInfo GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url)
{ {
if (File.Exists(responseCachePath) try
&& _fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
{ {
var stream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true); if (_fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
return new HttpResponseInfo
{ {
ResponseUrl = url, var stream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true);
Content = stream,
StatusCode = HttpStatusCode.OK, return new HttpResponseInfo
ContentLength = stream.Length {
}; ResponseUrl = url,
Content = stream,
StatusCode = HttpStatusCode.OK,
ContentLength = stream.Length
};
}
}
catch (FileNotFoundException) // REVIEW: @bond Is this really faster?
{
}
catch (DirectoryNotFoundException)
{
} }
return null; return null;
@@ -283,7 +315,7 @@ namespace Emby.Server.Implementations.HttpClientManager
private async Task CacheResponse(HttpResponseInfo response, string responseCachePath) private async Task CacheResponse(HttpResponseInfo response, string responseCachePath)
{ {
Directory.CreateDirectory(Path.GetDirectoryName(responseCachePath)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(responseCachePath));
using (var fileStream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.None, true)) using (var fileStream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.None, true))
{ {
@@ -352,11 +384,11 @@ namespace Emby.Server.Implementations.HttpClientManager
{ {
if (options.LogRequestAsDebug) if (options.LogRequestAsDebug)
{ {
_logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url); _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToUpper(), options.Url);
} }
else else
{ {
_logger.LogInformation("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url); _logger.LogInformation("HttpClientManager {0}: {1}", httpMethod.ToUpper(), options.Url);
} }
} }
@@ -491,7 +523,7 @@ namespace Emby.Server.Implementations.HttpClientManager
{ {
ValidateParams(options); ValidateParams(options);
Directory.CreateDirectory(_appPaths.TempDirectory); _fileSystem.CreateDirectory(_appPaths.TempDirectory);
var tempFile = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp"); var tempFile = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp");
@@ -605,16 +637,14 @@ namespace Emby.Server.Implementations.HttpClientManager
var exception = new HttpException(webException.Message, webException); var exception = new HttpException(webException.Message, webException);
using (var response = webException.Response as HttpWebResponse) var response = webException.Response as HttpWebResponse;
if (response != null)
{ {
if (response != null) exception.StatusCode = response.StatusCode;
{
exception.StatusCode = response.StatusCode;
if ((int)response.StatusCode == 429) if ((int)response.StatusCode == 429)
{ {
client.LastTimeout = DateTime.UtcNow; client.LastTimeout = DateTime.UtcNow;
}
} }
} }
@@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.HttpServer
private long RangeStart { get; set; } private long RangeStart { get; set; }
private long RangeEnd { get; set; } private long RangeEnd { get; set; }
private long RangeLength { get; set; } private long RangeLength { get; set; }
public long TotalContentLength { get; set; } private long TotalContentLength { get; set; }
public Action OnComplete { get; set; } public Action OnComplete { get; set; }
public Action OnError { get; set; } public Action OnError { get; set; }
@@ -5,7 +5,6 @@ using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Net; using System.Net;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
@@ -394,7 +393,7 @@ namespace Emby.Server.Implementations.HttpServer
{ {
return contentType == null return contentType == null
? null ? null
: contentType.Split(';')[0].ToLowerInvariant().Trim(); : contentType.Split(';')[0].ToLower().Trim();
} }
private static string SerializeToXmlString(object from) private static string SerializeToXmlString(object from)
@@ -422,20 +421,20 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary> /// <summary>
/// Pres the process optimized result. /// Pres the process optimized result.
/// </summary> /// </summary>
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, StaticResultOptions options) private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
{ {
responseHeaders["ETag"] = string.Format("\"{0}\"", cacheKeyString);
bool noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1; 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 (!noCache)
{ {
DateTime.TryParse(requestContext.Headers.Get("If-Modified-Since"), out var ifModifiedSinceHeader); if (IsNotModified(requestContext, cacheKey, lastDateModified, cacheDuration))
if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified))
{ {
AddAgeHeader(responseHeaders, options.DateLastModified); AddAgeHeader(responseHeaders, lastDateModified);
AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration);
var result = new HttpResult(Array.Empty<byte>(), options.ContentType ?? "text/html", HttpStatusCode.NotModified); var result = new HttpResult(Array.Empty<byte>(), contentType ?? "text/html", HttpStatusCode.NotModified);
AddResponseHeaders(result, responseHeaders); AddResponseHeaders(result, responseHeaders);
@@ -443,6 +442,8 @@ namespace Emby.Server.Implementations.HttpServer
} }
} }
AddCachingHeaders(responseHeaders, cacheKeyString, lastDateModified, cacheDuration);
return null; return null;
} }
@@ -487,6 +488,9 @@ namespace Emby.Server.Implementations.HttpServer
options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path); options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path);
} }
var cacheKey = path + options.DateLastModified.Value.Ticks;
options.CacheKey = cacheKey.GetMD5();
options.ContentFactory = () => Task.FromResult(GetFileStream(path, fileShare)); options.ContentFactory = () => Task.FromResult(GetFileStream(path, fileShare));
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@@ -517,6 +521,7 @@ namespace Emby.Server.Implementations.HttpServer
return GetStaticResult(requestContext, new StaticResultOptions return GetStaticResult(requestContext, new StaticResultOptions
{ {
CacheDuration = cacheDuration, CacheDuration = cacheDuration,
CacheKey = cacheKey,
ContentFactory = factoryFn, ContentFactory = factoryFn,
ContentType = contentType, ContentType = contentType,
DateLastModified = lastDateModified, DateLastModified = lastDateModified,
@@ -527,13 +532,16 @@ namespace Emby.Server.Implementations.HttpServer
public async Task<object> GetStaticResult(IRequest requestContext, StaticResultOptions options) public async Task<object> GetStaticResult(IRequest requestContext, StaticResultOptions options)
{ {
var cacheKey = options.CacheKey;
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var contentType = options.ContentType; var contentType = options.ContentType;
if (!string.IsNullOrEmpty(requestContext.Headers.Get("If-Modified-Since")))
if (!cacheKey.Equals(Guid.Empty))
{ {
var key = cacheKey.ToString("N");
// See if the result is already cached in the browser // See if the result is already cached in the browser
var result = GetCachedResult(requestContext, options.ResponseHeaders, options); var result = GetCachedResult(requestContext, options.ResponseHeaders, cacheKey, key, options.DateLastModified, options.CacheDuration, contentType);
if (result != null) if (result != null)
{ {
@@ -545,8 +553,8 @@ namespace Emby.Server.Implementations.HttpServer
var isHeadRequest = options.IsHeadRequest || string.Equals(requestContext.Verb, "HEAD", StringComparison.OrdinalIgnoreCase); var isHeadRequest = options.IsHeadRequest || string.Equals(requestContext.Verb, "HEAD", StringComparison.OrdinalIgnoreCase);
var factoryFn = options.ContentFactory; var factoryFn = options.ContentFactory;
var responseHeaders = options.ResponseHeaders; var responseHeaders = options.ResponseHeaders;
AddCachingHeaders(responseHeaders, options.CacheDuration, false, options.DateLastModified);
AddAgeHeader(responseHeaders, options.DateLastModified); //var requestedCompressionType = GetCompressionType(requestContext);
var rangeHeader = requestContext.Headers.Get("Range"); var rangeHeader = requestContext.Headers.Get("Range");
@@ -622,28 +630,45 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary> /// <summary>
/// Adds the caching responseHeaders. /// Adds the caching responseHeaders.
/// </summary> /// </summary>
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, TimeSpan? cacheDuration, private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
bool noCache, DateTime? lastModifiedDate)
{ {
if (noCache) // Don't specify both last modified and Etag, unless caching unconditionally. They are redundant
// https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching
if (lastDateModified.HasValue && (string.IsNullOrEmpty(cacheKey) || cacheDuration.HasValue))
{ {
responseHeaders["Cache-Control"] = "no-cache, no-store, must-revalidate"; AddAgeHeader(responseHeaders, lastDateModified);
responseHeaders["pragma"] = "no-cache, no-store, must-revalidate"; responseHeaders["Last-Modified"] = lastDateModified.Value.ToString("r");
return;
} }
if (cacheDuration.HasValue) if (cacheDuration.HasValue)
{ {
responseHeaders["Cache-Control"] = "public, max-age=" + cacheDuration.Value.TotalSeconds; responseHeaders["Cache-Control"] = "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds);
} }
else else if (!string.IsNullOrEmpty(cacheKey))
{ {
responseHeaders["Cache-Control"] = "public"; responseHeaders["Cache-Control"] = "public";
} }
else
if (lastModifiedDate.HasValue)
{ {
responseHeaders["Last-Modified"] = lastModifiedDate.ToString(); responseHeaders["Cache-Control"] = "no-cache, no-store, must-revalidate";
responseHeaders["pragma"] = "no-cache, no-store, must-revalidate";
}
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");
}
else if (string.IsNullOrEmpty(cacheKey))
{
responseHeaders["Expires"] = "-1";
} }
} }
@@ -659,6 +684,45 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders["Age"] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture); 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, DateTime? lastDateModified, TimeSpan? cacheDuration)
{
//var isNotModified = true;
var ifModifiedSinceHeader = requestContext.Headers.Get("If-Modified-Since");
if (!string.IsNullOrEmpty(ifModifiedSinceHeader)
&& DateTime.TryParse(ifModifiedSinceHeader, out DateTime ifModifiedSince)
&& IsNotModified(ifModifiedSince.ToUniversalTime(), cacheDuration, lastDateModified))
{
return true;
}
var ifNoneMatchHeader = requestContext.Headers.Get("If-None-Match");
bool hasCacheKey = !cacheKey.Equals(Guid.Empty);
// Validate If-None-Match
if ((hasCacheKey && !string.IsNullOrEmpty(ifNoneMatchHeader)))
{
ifNoneMatchHeader = (ifNoneMatchHeader ?? string.Empty).Trim('\"');
if (Guid.TryParse(ifNoneMatchHeader, out var ifNoneMatch)
&& cacheKey.Equals(ifNoneMatch))
{
return true;
}
}
return false;
}
/// <summary> /// <summary>
/// Determines whether [is not modified] [the specified if modified since]. /// Determines whether [is not modified] [the specified if modified since].
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@@ -10,6 +9,7 @@ using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.System; using MediaBrowser.Model.System;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.IO namespace Emby.Server.Implementations.IO
@@ -22,7 +22,8 @@ namespace Emby.Server.Implementations.IO
private IServerConfigurationManager ConfigurationManager { get; set; } private IServerConfigurationManager ConfigurationManager { get; set; }
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly List<string> _affectedPaths = new List<string>(); private readonly List<string> _affectedPaths = new List<string>();
private Timer _timer; private ITimer _timer;
private readonly ITimerFactory _timerFactory;
private readonly object _timerLock = new object(); private readonly object _timerLock = new object();
public string Path { get; private set; } public string Path { get; private set; }
@@ -30,7 +31,7 @@ namespace Emby.Server.Implementations.IO
private readonly IEnvironmentInfo _environmentInfo; private readonly IEnvironmentInfo _environmentInfo;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, IEnvironmentInfo environmentInfo, ILibraryManager libraryManager1) public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo, ILibraryManager libraryManager1)
{ {
logger.LogDebug("New file refresher created for {0}", path); logger.LogDebug("New file refresher created for {0}", path);
Path = path; Path = path;
@@ -40,6 +41,7 @@ namespace Emby.Server.Implementations.IO
LibraryManager = libraryManager; LibraryManager = libraryManager;
TaskManager = taskManager; TaskManager = taskManager;
Logger = logger; Logger = logger;
_timerFactory = timerFactory;
_environmentInfo = environmentInfo; _environmentInfo = environmentInfo;
_libraryManager = libraryManager1; _libraryManager = libraryManager1;
AddPath(path); AddPath(path);
@@ -88,7 +90,7 @@ namespace Emby.Server.Implementations.IO
if (_timer == null) if (_timer == null)
{ {
_timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); _timer = _timerFactory.Create(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
} }
else else
{ {
@@ -144,8 +146,8 @@ namespace Emby.Server.Implementations.IO
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)
.Select(GetAffectedBaseItem) .Select(GetAffectedBaseItem)
.Where(item => item != null) .Where(item => item != null)
.GroupBy(x => x.Id) .DistinctBy(i => i.Id)
.Select(x => x.First()); .ToList();
foreach (var item in itemsToRefresh) foreach (var item in itemsToRefresh)
{ {
@@ -187,13 +189,13 @@ namespace Emby.Server.Implementations.IO
{ {
item = LibraryManager.FindByPath(path, null); item = LibraryManager.FindByPath(path, null);
path = System.IO.Path.GetDirectoryName(path); path = _fileSystem.GetDirectoryName(path);
} }
if (item != null) if (item != null)
{ {
// If the item has been deleted find the first valid parent that still exists // If the item has been deleted find the first valid parent that still exists
while (!Directory.Exists(item.Path) && !File.Exists(item.Path)) while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path))
{ {
item = item.GetOwner() ?? item.GetParent(); item = item.GetOwner() ?? item.GetParent();
@@ -11,6 +11,7 @@ using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.System; using MediaBrowser.Model.System;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.IO namespace Emby.Server.Implementations.IO
@@ -34,7 +35,7 @@ namespace Emby.Server.Implementations.IO
/// <summary> /// <summary>
/// Any file name ending in any of these will be ignored by the watchers /// Any file name ending in any of these will be ignored by the watchers
/// </summary> /// </summary>
private readonly HashSet<string> _alwaysIgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase) private readonly string[] _alwaysIgnoreFiles = new string[]
{ {
"small.jpg", "small.jpg",
"albumart.jpg", "albumart.jpg",
@@ -53,7 +54,7 @@ namespace Emby.Server.Implementations.IO
".actors" ".actors"
}; };
private readonly HashSet<string> _alwaysIgnoreExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) private readonly string[] _alwaysIgnoreExtensions = new string[]
{ {
// thumbs.db // thumbs.db
".db", ".db",
@@ -133,18 +134,13 @@ namespace Emby.Server.Implementations.IO
private IServerConfigurationManager ConfigurationManager { get; set; } private IServerConfigurationManager ConfigurationManager { get; set; }
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ITimerFactory _timerFactory;
private readonly IEnvironmentInfo _environmentInfo; private readonly IEnvironmentInfo _environmentInfo;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LibraryMonitor" /> class. /// Initializes a new instance of the <see cref="LibraryMonitor" /> class.
/// </summary> /// </summary>
public LibraryMonitor( public LibraryMonitor(ILoggerFactory loggerFactory, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ITimerFactory timerFactory, ISystemEvents systemEvents, IEnvironmentInfo environmentInfo)
ILoggerFactory loggerFactory,
ITaskManager taskManager,
ILibraryManager libraryManager,
IServerConfigurationManager configurationManager,
IFileSystem fileSystem,
IEnvironmentInfo environmentInfo)
{ {
if (taskManager == null) if (taskManager == null)
{ {
@@ -156,10 +152,28 @@ namespace Emby.Server.Implementations.IO
Logger = loggerFactory.CreateLogger(GetType().Name); Logger = loggerFactory.CreateLogger(GetType().Name);
ConfigurationManager = configurationManager; ConfigurationManager = configurationManager;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_timerFactory = timerFactory;
_environmentInfo = environmentInfo; _environmentInfo = environmentInfo;
systemEvents.Resume += _systemEvents_Resume;
} }
private bool IsLibraryMonitorEnabled(BaseItem item) private void _systemEvents_Resume(object sender, EventArgs e)
{
Restart();
}
private void Restart()
{
Stop();
if (!_disposed)
{
Start();
}
}
private bool IsLibraryMonitorEnabaled(BaseItem item)
{ {
if (item is BasePluginFolder) if (item is BasePluginFolder)
{ {
@@ -186,7 +200,7 @@ namespace Emby.Server.Implementations.IO
var paths = LibraryManager var paths = LibraryManager
.RootFolder .RootFolder
.Children .Children
.Where(IsLibraryMonitorEnabled) .Where(IsLibraryMonitorEnabaled)
.OfType<Folder>() .OfType<Folder>()
.SelectMany(f => f.PhysicalLocations) .SelectMany(f => f.PhysicalLocations)
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)
@@ -209,7 +223,7 @@ namespace Emby.Server.Implementations.IO
private void StartWatching(BaseItem item) private void StartWatching(BaseItem item)
{ {
if (IsLibraryMonitorEnabled(item)) if (IsLibraryMonitorEnabaled(item))
{ {
StartWatchingPath(item.Path); StartWatchingPath(item.Path);
} }
@@ -273,7 +287,7 @@ namespace Emby.Server.Implementations.IO
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
private void StartWatchingPath(string path) private void StartWatchingPath(string path)
{ {
if (!Directory.Exists(path)) if (!_fileSystem.DirectoryExists(path))
{ {
// Seeing a crash in the mono runtime due to an exception being thrown on a different thread // 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); Logger.LogInformation("Skipping realtime monitor for {0} because the path does not exist", path);
@@ -456,8 +470,8 @@ namespace Emby.Server.Implementations.IO
var filename = Path.GetFileName(path); var filename = Path.GetFileName(path);
var monitorPath = !string.IsNullOrEmpty(filename) && var monitorPath = !string.IsNullOrEmpty(filename) &&
!_alwaysIgnoreFiles.Contains(filename) && !_alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase) &&
!_alwaysIgnoreExtensions.Contains(Path.GetExtension(path)) && !_alwaysIgnoreExtensions.Contains(Path.GetExtension(path) ?? string.Empty, StringComparer.OrdinalIgnoreCase) &&
_alwaysIgnoreSubstrings.All(i => path.IndexOf(i, StringComparison.OrdinalIgnoreCase) == -1); _alwaysIgnoreSubstrings.All(i => path.IndexOf(i, StringComparison.OrdinalIgnoreCase) == -1);
// Ignore certain files // Ignore certain files
@@ -479,7 +493,7 @@ namespace Emby.Server.Implementations.IO
} }
// Go up a level // Go up a level
var parent = Path.GetDirectoryName(i); var parent = _fileSystem.GetDirectoryName(i);
if (!string.IsNullOrEmpty(parent)) if (!string.IsNullOrEmpty(parent))
{ {
if (_fileSystem.AreEqual(parent, path)) if (_fileSystem.AreEqual(parent, path))
@@ -505,7 +519,7 @@ namespace Emby.Server.Implementations.IO
private void CreateRefresher(string path) private void CreateRefresher(string path)
{ {
var parentPath = Path.GetDirectoryName(path); var parentPath = _fileSystem.GetDirectoryName(path);
lock (_activeRefreshers) lock (_activeRefreshers)
{ {
@@ -534,14 +548,14 @@ namespace Emby.Server.Implementations.IO
} }
// They are siblings. Rebase the refresher to the parent folder. // They are siblings. Rebase the refresher to the parent folder.
if (string.Equals(parentPath, Path.GetDirectoryName(refresher.Path), StringComparison.Ordinal)) if (string.Equals(parentPath, _fileSystem.GetDirectoryName(refresher.Path), StringComparison.Ordinal))
{ {
refresher.ResetPath(parentPath, path); refresher.ResetPath(parentPath, path);
return; return;
} }
} }
var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _environmentInfo, LibraryManager); var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _timerFactory, _environmentInfo, LibraryManager);
newRefresher.Completed += NewRefresher_Completed; newRefresher.Completed += NewRefresher_Completed;
_activeRefreshers.Add(newRefresher); _activeRefreshers.Add(newRefresher);
} }
@@ -597,26 +611,20 @@ namespace Emby.Server.Implementations.IO
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
_disposed = true;
Dispose(true); Dispose(true);
} }
/// <summary> /// <summary>
/// Releases unmanaged and - optionally - managed resources. /// Releases unmanaged and - optionally - managed resources.
/// </summary> /// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> /// <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 disposing) protected virtual void Dispose(bool dispose)
{ {
if (_disposed) if (dispose)
{
return;
}
if (disposing)
{ {
Stop(); Stop();
} }
_disposed = true;
} }
} }
@@ -629,10 +637,9 @@ namespace Emby.Server.Implementations.IO
_monitor = monitor; _monitor = monitor;
} }
public Task RunAsync() public void Run()
{ {
_monitor.Start(); _monitor.Start();
return Task.CompletedTask;
} }
public void Dispose() public void Dispose()
@@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.IO
_isEnvironmentCaseInsensitive = environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows; _isEnvironmentCaseInsensitive = environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows;
} }
public virtual string DefaultDirectory public string DefaultDirectory
{ {
get get
{ {
@@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.IO
{ {
try try
{ {
if (Directory.Exists(value)) if (DirectoryExists(value))
{ {
return value; return value;
} }
@@ -75,7 +75,7 @@ namespace Emby.Server.Implementations.IO
} }
} }
public virtual void AddShortcutHandler(IShortcutHandler handler) public void AddShortcutHandler(IShortcutHandler handler)
{ {
_shortcutHandlers.Add(handler); _shortcutHandlers.Add(handler);
} }
@@ -94,6 +94,13 @@ namespace Emby.Server.Implementations.IO
} }
} }
public char DirectorySeparatorChar => Path.DirectorySeparatorChar;
public string GetFullPath(string path)
{
return Path.GetFullPath(path);
}
/// <summary> /// <summary>
/// Determines whether the specified filename is shortcut. /// Determines whether the specified filename is shortcut.
/// </summary> /// </summary>
@@ -135,7 +142,7 @@ namespace Emby.Server.Implementations.IO
return null; return null;
} }
public virtual string MakeAbsolutePath(string folderPath, string filePath) public string MakeAbsolutePath(string folderPath, string filePath)
{ {
if (string.IsNullOrWhiteSpace(filePath)) return filePath; if (string.IsNullOrWhiteSpace(filePath)) return filePath;
@@ -188,7 +195,7 @@ namespace Emby.Server.Implementations.IO
/// or /// or
/// target /// target
/// </exception> /// </exception>
public virtual void CreateShortcut(string shortcutPath, string target) public void CreateShortcut(string shortcutPath, string target)
{ {
if (string.IsNullOrEmpty(shortcutPath)) if (string.IsNullOrEmpty(shortcutPath))
{ {
@@ -220,7 +227,7 @@ namespace Emby.Server.Implementations.IO
/// <returns>A <see cref="FileSystemMetadata"/> object.</returns> /// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
/// <remarks>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> object's /// <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> /// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and all other properties will reflect the properties of the directory.</remarks>
public virtual FileSystemMetadata GetFileSystemInfo(string path) public FileSystemMetadata GetFileSystemInfo(string path)
{ {
// Take a guess to try and avoid two file system hits, but we'll double-check by calling Exists // Take a guess to try and avoid two file system hits, but we'll double-check by calling Exists
if (Path.HasExtension(path)) if (Path.HasExtension(path))
@@ -255,7 +262,7 @@ namespace Emby.Server.Implementations.IO
/// <remarks><para>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> object's /// <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> /// <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> /// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></remarks>
public virtual FileSystemMetadata GetFileInfo(string path) public FileSystemMetadata GetFileInfo(string path)
{ {
var fileInfo = new FileInfo(path); var fileInfo = new FileInfo(path);
@@ -270,7 +277,7 @@ namespace Emby.Server.Implementations.IO
/// <remarks><para>If the specified path points to a file, the returned <see cref="FileSystemMetadata"/> object's /// <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> /// <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> /// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></remarks>
public virtual FileSystemMetadata GetDirectoryInfo(string path) public FileSystemMetadata GetDirectoryInfo(string path)
{ {
var fileInfo = new DirectoryInfo(path); var fileInfo = new DirectoryInfo(path);
@@ -332,19 +339,24 @@ namespace Emby.Server.Implementations.IO
return result; return result;
} }
/// <summary>
/// The space char
/// </summary>
private const char SpaceChar = ' ';
/// <summary> /// <summary>
/// Takes a filename and removes invalid characters /// Takes a filename and removes invalid characters
/// </summary> /// </summary>
/// <param name="filename">The filename.</param> /// <param name="filename">The filename.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
/// <exception cref="ArgumentNullException">filename</exception> /// <exception cref="ArgumentNullException">filename</exception>
public virtual string GetValidFilename(string filename) public string GetValidFilename(string filename)
{ {
var builder = new StringBuilder(filename); var builder = new StringBuilder(filename);
foreach (var c in _invalidFileNameChars) foreach (var c in _invalidFileNameChars)
{ {
builder = builder.Replace(c, ' '); builder = builder.Replace(c, SpaceChar);
} }
return builder.ToString(); return builder.ToString();
@@ -374,17 +386,17 @@ namespace Emby.Server.Implementations.IO
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
/// <returns>DateTime.</returns> /// <returns>DateTime.</returns>
public virtual DateTime GetCreationTimeUtc(string path) public DateTime GetCreationTimeUtc(string path)
{ {
return GetCreationTimeUtc(GetFileSystemInfo(path)); return GetCreationTimeUtc(GetFileSystemInfo(path));
} }
public virtual DateTime GetCreationTimeUtc(FileSystemMetadata info) public DateTime GetCreationTimeUtc(FileSystemMetadata info)
{ {
return info.CreationTimeUtc; return info.CreationTimeUtc;
} }
public virtual DateTime GetLastWriteTimeUtc(FileSystemMetadata info) public DateTime GetLastWriteTimeUtc(FileSystemMetadata info)
{ {
return info.LastWriteTimeUtc; return info.LastWriteTimeUtc;
} }
@@ -413,7 +425,7 @@ namespace Emby.Server.Implementations.IO
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
/// <returns>DateTime.</returns> /// <returns>DateTime.</returns>
public virtual DateTime GetLastWriteTimeUtc(string path) public DateTime GetLastWriteTimeUtc(string path)
{ {
return GetLastWriteTimeUtc(GetFileSystemInfo(path)); return GetLastWriteTimeUtc(GetFileSystemInfo(path));
} }
@@ -427,7 +439,7 @@ namespace Emby.Server.Implementations.IO
/// <param name="share">The share.</param> /// <param name="share">The share.</param>
/// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param> /// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
/// <returns>FileStream.</returns> /// <returns>FileStream.</returns>
public virtual Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, bool isAsync = false) public Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, bool isAsync = false)
{ {
if (_supportsAsyncFileStreams && isAsync) if (_supportsAsyncFileStreams && isAsync)
{ {
@@ -437,7 +449,7 @@ namespace Emby.Server.Implementations.IO
return GetFileStream(path, mode, access, share, FileOpenOptions.None); return GetFileStream(path, mode, access, share, FileOpenOptions.None);
} }
public virtual Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, FileOpenOptions fileOpenOptions) public Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, FileOpenOptions fileOpenOptions)
=> new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 4096, GetFileOptions(fileOpenOptions)); => new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 4096, GetFileOptions(fileOpenOptions));
private static FileOptions GetFileOptions(FileOpenOptions mode) private static FileOptions GetFileOptions(FileOpenOptions mode)
@@ -499,7 +511,7 @@ namespace Emby.Server.Implementations.IO
} }
} }
public virtual void SetHidden(string path, bool isHidden) public void SetHidden(string path, bool isHidden)
{ {
if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows) if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
{ {
@@ -523,7 +535,7 @@ namespace Emby.Server.Implementations.IO
} }
} }
public virtual void SetReadOnly(string path, bool isReadOnly) public void SetReadOnly(string path, bool isReadOnly)
{ {
if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows) if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
{ {
@@ -547,7 +559,7 @@ namespace Emby.Server.Implementations.IO
} }
} }
public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly) public void SetAttributes(string path, bool isHidden, bool isReadOnly)
{ {
if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows) if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
{ {
@@ -599,7 +611,7 @@ namespace Emby.Server.Implementations.IO
/// </summary> /// </summary>
/// <param name="file1">The file1.</param> /// <param name="file1">The file1.</param>
/// <param name="file2">The file2.</param> /// <param name="file2">The file2.</param>
public virtual void SwapFiles(string file1, string file2) public void SwapFiles(string file1, string file2)
{ {
if (string.IsNullOrEmpty(file1)) if (string.IsNullOrEmpty(file1))
{ {
@@ -618,13 +630,18 @@ namespace Emby.Server.Implementations.IO
SetHidden(file2, false); SetHidden(file2, false);
Directory.CreateDirectory(_tempPath); Directory.CreateDirectory(_tempPath);
File.Copy(file1, temp1, true); CopyFile(file1, temp1, true);
File.Copy(file2, file1, true); CopyFile(file2, file1, true);
File.Copy(temp1, file2, true); CopyFile(temp1, file2, true);
} }
public virtual bool ContainsSubPath(string parentPath, string path) private static char GetDirectorySeparatorChar(string path)
{
return Path.DirectorySeparatorChar;
}
public bool ContainsSubPath(string parentPath, string path)
{ {
if (string.IsNullOrEmpty(parentPath)) if (string.IsNullOrEmpty(parentPath))
{ {
@@ -636,19 +653,19 @@ namespace Emby.Server.Implementations.IO
throw new ArgumentNullException(nameof(path)); throw new ArgumentNullException(nameof(path));
} }
var separatorChar = Path.DirectorySeparatorChar; var separatorChar = GetDirectorySeparatorChar(parentPath);
return path.IndexOf(parentPath.TrimEnd(separatorChar) + separatorChar, StringComparison.OrdinalIgnoreCase) != -1; return path.IndexOf(parentPath.TrimEnd(separatorChar) + separatorChar, StringComparison.OrdinalIgnoreCase) != -1;
} }
public virtual bool IsRootPath(string path) public bool IsRootPath(string path)
{ {
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
{ {
throw new ArgumentNullException(nameof(path)); throw new ArgumentNullException(nameof(path));
} }
var parent = Path.GetDirectoryName(path); var parent = GetDirectoryName(path);
if (!string.IsNullOrEmpty(parent)) if (!string.IsNullOrEmpty(parent))
{ {
@@ -658,7 +675,12 @@ namespace Emby.Server.Implementations.IO
return true; return true;
} }
public virtual string NormalizePath(string path) public string GetDirectoryName(string path)
{
return Path.GetDirectoryName(path);
}
public string NormalizePath(string path)
{ {
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
{ {
@@ -670,10 +692,10 @@ namespace Emby.Server.Implementations.IO
return path; return path;
} }
return path.TrimEnd(Path.DirectorySeparatorChar); return path.TrimEnd(GetDirectorySeparatorChar(path));
} }
public virtual bool AreEqual(string path1, string path2) public bool AreEqual(string path1, string path2)
{ {
if (path1 == null && path2 == null) if (path1 == null && path2 == null)
{ {
@@ -688,7 +710,7 @@ namespace Emby.Server.Implementations.IO
return string.Equals(NormalizePath(path1), NormalizePath(path2), StringComparison.OrdinalIgnoreCase); return string.Equals(NormalizePath(path1), NormalizePath(path2), StringComparison.OrdinalIgnoreCase);
} }
public virtual string GetFileNameWithoutExtension(FileSystemMetadata info) public string GetFileNameWithoutExtension(FileSystemMetadata info)
{ {
if (info.IsDirectory) if (info.IsDirectory)
{ {
@@ -698,7 +720,12 @@ namespace Emby.Server.Implementations.IO
return Path.GetFileNameWithoutExtension(info.FullName); return Path.GetFileNameWithoutExtension(info.FullName);
} }
public virtual bool IsPathFile(string path) public string GetFileNameWithoutExtension(string path)
{
return Path.GetFileNameWithoutExtension(path);
}
public bool IsPathFile(string path)
{ {
// Cannot use Path.IsPathRooted because it returns false under mono when using windows-based paths, e.g. C:\\ // Cannot use Path.IsPathRooted because it returns false under mono when using windows-based paths, e.g. C:\\
@@ -713,13 +740,23 @@ namespace Emby.Server.Implementations.IO
//return Path.IsPathRooted(path); //return Path.IsPathRooted(path);
} }
public virtual void DeleteFile(string path) public void DeleteFile(string path)
{ {
SetAttributes(path, false, false); SetAttributes(path, false, false);
File.Delete(path); File.Delete(path);
} }
public virtual List<FileSystemMetadata> GetDrives() public void DeleteDirectory(string path, bool recursive)
{
Directory.Delete(path, recursive);
}
public void CreateDirectory(string path)
{
Directory.CreateDirectory(path);
}
public List<FileSystemMetadata> GetDrives()
{ {
// Only include drives in the ready state or this method could end up being very slow, waiting for drives to timeout // 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 return DriveInfo.GetDrives().Where(d => d.IsReady).Select(d => new FileSystemMetadata
@@ -731,19 +768,19 @@ namespace Emby.Server.Implementations.IO
}).ToList(); }).ToList();
} }
public virtual IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false) public IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
{ {
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", searchOption)); return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", searchOption));
} }
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false) public IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false)
{ {
return GetFiles(path, null, false, recursive); return GetFiles(path, null, false, recursive);
} }
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false) public IEnumerable<FileSystemMetadata> GetFiles(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{ {
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
@@ -772,7 +809,7 @@ namespace Emby.Server.Implementations.IO
return ToMetadata(files); return ToMetadata(files);
} }
public virtual IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false) public IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
{ {
var directoryInfo = new DirectoryInfo(path); var directoryInfo = new DirectoryInfo(path);
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
@@ -790,19 +827,89 @@ namespace Emby.Server.Implementations.IO
{ {
return infos.Select(GetFileSystemMetadata); return infos.Select(GetFileSystemMetadata);
} }
public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false) 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)
{ {
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
return Directory.EnumerateDirectories(path, "*", searchOption); return Directory.EnumerateDirectories(path, "*", searchOption);
} }
public virtual IEnumerable<string> GetFilePaths(string path, bool recursive = false) public IEnumerable<string> GetFilePaths(string path, bool recursive = false)
{ {
return GetFilePaths(path, null, false, recursive); return GetFilePaths(path, null, false, recursive);
} }
public virtual IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false) public IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{ {
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
@@ -831,7 +938,7 @@ namespace Emby.Server.Implementations.IO
return files; return files;
} }
public virtual IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false) public IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
{ {
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
return Directory.EnumerateFileSystemEntries(path, "*", searchOption); return Directory.EnumerateFileSystemEntries(path, "*", searchOption);
@@ -841,7 +948,7 @@ namespace Emby.Server.Implementations.IO
{ {
if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX) if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX)
{ {
RunProcess("chmod", "+x \"" + path + "\"", Path.GetDirectoryName(path)); RunProcess("chmod", "+x \"" + path + "\"", GetDirectoryName(path));
} }
} }
@@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.IO
if (string.Equals(Path.GetExtension(shortcutPath), ".mblink", StringComparison.OrdinalIgnoreCase)) if (string.Equals(Path.GetExtension(shortcutPath), ".mblink", StringComparison.OrdinalIgnoreCase))
{ {
var path = File.ReadAllText(shortcutPath); var path = _fileSystem.ReadAllText(shortcutPath);
return _fileSystem.NormalizePath(path); return _fileSystem.NormalizePath(path);
} }
@@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.IO
throw new ArgumentNullException(nameof(targetPath)); throw new ArgumentNullException(nameof(targetPath));
} }
File.WriteAllText(shortcutPath, targetPath); _fileSystem.WriteAllText(shortcutPath, targetPath);
} }
} }
} }
@@ -1,40 +0,0 @@
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) CancellationToken cancellationToken)
{ {
var outputPathWithoutExtension = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N")); var outputPathWithoutExtension = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(Path.GetDirectoryName(outputPathWithoutExtension)); FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPathWithoutExtension));
string outputPath = CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0); string outputPath = CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0);
if (string.IsNullOrEmpty(outputPath)) 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) private string CreateCollage(BaseItem primaryItem, List<BaseItem> items, string outputPath, int width, int height)
{ {
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPath));
var options = new ImageCollageOptions var options = new ImageCollageOptions
{ {
@@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.Images
{ {
return CreateSquareCollage(item, itemsWithImages, outputPath); return CreateSquareCollage(item, itemsWithImages, outputPath);
} }
if (item is Playlist || item is MusicGenre || item is Genre || item is PhotoAlbum) if (item is Playlist || item is MusicGenre || item is Genre || item is GameGenre || item is PhotoAlbum)
{ {
return CreateSquareCollage(item, itemsWithImages, outputPath); return CreateSquareCollage(item, itemsWithImages, outputPath);
} }
@@ -300,7 +300,7 @@ namespace Emby.Server.Implementations.Images
var ext = Path.GetExtension(image); var ext = Path.GetExtension(image);
var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ext); var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ext);
File.Copy(image, outputPath, true); FileSystem.CopyFile(image, outputPath, true);
return outputPath; return outputPath;
} }
@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@@ -147,7 +146,7 @@ namespace Emby.Server.Implementations.Library
if (parent != null) if (parent != null)
{ {
// Don't resolve these into audio files // Don't resolve these into audio files
if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) && _libraryManager.IsAudioFile(filename)) if (string.Equals(_fileSystem.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) && _libraryManager.IsAudioFile(filename))
{ {
return true; return true;
} }
@@ -375,7 +375,7 @@ namespace Emby.Server.Implementations.Library
try try
{ {
Directory.Delete(metadataPath, true); _fileSystem.DeleteDirectory(metadataPath, true);
} }
catch (IOException) catch (IOException)
{ {
@@ -395,33 +395,38 @@ namespace Emby.Server.Implementations.Library
foreach (var fileSystemInfo in item.GetDeletePaths().ToList()) foreach (var fileSystemInfo in item.GetDeletePaths().ToList())
{ {
if (File.Exists(fileSystemInfo.FullName)) try
{ {
try _logger.LogDebug("Deleting path {path}", fileSystemInfo.FullName);
if (fileSystemInfo.IsDirectory)
{ {
_logger.LogDebug("Deleting path {path}", fileSystemInfo.FullName); _fileSystem.DeleteDirectory(fileSystemInfo.FullName, true);
if (fileSystemInfo.IsDirectory)
{
Directory.Delete(fileSystemInfo.FullName, true);
}
else
{
File.Delete(fileSystemInfo.FullName);
}
} }
catch (IOException) else
{ {
if (isRequiredForDelete) _fileSystem.DeleteFile(fileSystemInfo.FullName);
{
throw;
}
} }
catch (UnauthorizedAccessException) }
catch (FileNotFoundException)
{
// may have already been deleted manually by user
}
catch (DirectoryNotFoundException)
{
// may have already been deleted manually by user
}
catch (IOException)
{
if (isRequiredForDelete)
{ {
if (isRequiredForDelete) throw;
{ }
throw; }
} catch (UnauthorizedAccessException)
{
if (isRequiredForDelete)
{
throw;
} }
} }
@@ -512,7 +517,7 @@ namespace Emby.Server.Implementations.Library
if (forceCaseInsensitive || !ConfigurationManager.Configuration.EnableCaseSensitiveItemIds) if (forceCaseInsensitive || !ConfigurationManager.Configuration.EnableCaseSensitiveItemIds)
{ {
key = key.ToLowerInvariant(); key = key.ToLower();
} }
key = type.FullName + key; key = type.FullName + key;
@@ -720,7 +725,7 @@ namespace Emby.Server.Implementations.Library
{ {
var rootFolderPath = ConfigurationManager.ApplicationPaths.RootFolderPath; var rootFolderPath = ConfigurationManager.ApplicationPaths.RootFolderPath;
Directory.CreateDirectory(rootFolderPath); _fileSystem.CreateDirectory(rootFolderPath);
var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath))).DeepCopy<Folder, AggregateFolder>(); var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath))).DeepCopy<Folder, AggregateFolder>();
@@ -734,7 +739,7 @@ namespace Emby.Server.Implementations.Library
// Add in the plug-in folders // Add in the plug-in folders
var path = Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "playlists"); var path = Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "playlists");
Directory.CreateDirectory(path); _fileSystem.CreateDirectory(path);
Folder folder = new PlaylistsFolder Folder folder = new PlaylistsFolder
{ {
@@ -785,7 +790,7 @@ namespace Emby.Server.Implementations.Library
{ {
var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
Directory.CreateDirectory(userRootPath); _fileSystem.CreateDirectory(userRootPath);
var tmpItem = GetItemById(GetNewItemId(userRootPath, typeof(UserRootFolder))) as UserRootFolder; var tmpItem = GetItemById(GetNewItemId(userRootPath, typeof(UserRootFolder))) as UserRootFolder;
@@ -869,6 +874,11 @@ namespace Emby.Server.Implementations.Library
return GetItemByNameId<MusicGenre>(MusicGenre.GetPath, name); return GetItemByNameId<MusicGenre>(MusicGenre.GetPath, name);
} }
public Guid GetGameGenreId(string name)
{
return GetItemByNameId<GameGenre>(GameGenre.GetPath, name);
}
/// <summary> /// <summary>
/// Gets a Genre /// Gets a Genre
/// </summary> /// </summary>
@@ -889,6 +899,16 @@ namespace Emby.Server.Implementations.Library
return CreateItemByName<MusicGenre>(MusicGenre.GetPath, name, new DtoOptions(true)); 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> /// <summary>
/// Gets a Year /// Gets a Year
/// </summary> /// </summary>
@@ -984,7 +1004,7 @@ namespace Emby.Server.Implementations.Library
public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress) public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
{ {
// Ensure the location is available. // Ensure the location is available.
Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath); _fileSystem.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath);
return new PeopleValidator(this, _logger, ConfigurationManager, _fileSystem).ValidatePeople(cancellationToken, progress); return new PeopleValidator(this, _logger, ConfigurationManager, _fileSystem).ValidatePeople(cancellationToken, progress);
} }
@@ -1213,7 +1233,7 @@ namespace Emby.Server.Implementations.Library
private string GetCollectionType(string path) private string GetCollectionType(string path)
{ {
return _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false) return _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false)
.Select(i => Path.GetFileNameWithoutExtension(i)) .Select(i => _fileSystem.GetFileNameWithoutExtension(i))
.FirstOrDefault(i => !string.IsNullOrEmpty(i)); .FirstOrDefault(i => !string.IsNullOrEmpty(i));
} }
@@ -1355,6 +1375,17 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetGenres(query); 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) public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query)
{ {
if (query.User != null) if (query.User != null)
@@ -2120,7 +2151,7 @@ namespace Emby.Server.Implementations.Library
if (item == null || !string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase)) if (item == null || !string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase))
{ {
Directory.CreateDirectory(path); _fileSystem.CreateDirectory(path);
item = new UserView item = new UserView
{ {
@@ -2165,7 +2196,7 @@ namespace Emby.Server.Implementations.Library
if (item == null) if (item == null)
{ {
Directory.CreateDirectory(path); _fileSystem.CreateDirectory(path);
item = new UserView item = new UserView
{ {
@@ -2230,7 +2261,7 @@ namespace Emby.Server.Implementations.Library
if (item == null) if (item == null)
{ {
Directory.CreateDirectory(path); _fileSystem.CreateDirectory(path);
item = new UserView item = new UserView
{ {
@@ -2298,7 +2329,7 @@ namespace Emby.Server.Implementations.Library
if (item == null) if (item == null)
{ {
Directory.CreateDirectory(path); _fileSystem.CreateDirectory(path);
item = new UserView item = new UserView
{ {
@@ -2837,7 +2868,7 @@ namespace Emby.Server.Implementations.Library
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, name); var virtualFolderPath = Path.Combine(rootFolderPath, name);
while (Directory.Exists(virtualFolderPath)) while (_fileSystem.DirectoryExists(virtualFolderPath))
{ {
name += "1"; name += "1";
virtualFolderPath = Path.Combine(rootFolderPath, name); virtualFolderPath = Path.Combine(rootFolderPath, name);
@@ -2846,7 +2877,7 @@ namespace Emby.Server.Implementations.Library
var mediaPathInfos = options.PathInfos; var mediaPathInfos = options.PathInfos;
if (mediaPathInfos != null) if (mediaPathInfos != null)
{ {
var invalidpath = mediaPathInfos.FirstOrDefault(i => !Directory.Exists(i.Path)); var invalidpath = mediaPathInfos.FirstOrDefault(i => !_fileSystem.DirectoryExists(i.Path));
if (invalidpath != null) if (invalidpath != null)
{ {
throw new ArgumentException("The specified path does not exist: " + invalidpath.Path + "."); throw new ArgumentException("The specified path does not exist: " + invalidpath.Path + ".");
@@ -2857,13 +2888,13 @@ namespace Emby.Server.Implementations.Library
try try
{ {
Directory.CreateDirectory(virtualFolderPath); _fileSystem.CreateDirectory(virtualFolderPath);
if (!string.IsNullOrEmpty(collectionType)) if (!string.IsNullOrEmpty(collectionType))
{ {
var path = Path.Combine(virtualFolderPath, collectionType + ".collection"); var path = Path.Combine(virtualFolderPath, collectionType + ".collection");
File.WriteAllBytes(path, Array.Empty<byte>()); _fileSystem.WriteAllBytes(path, Array.Empty<byte>());
} }
CollectionFolder.SaveLibraryOptions(virtualFolderPath, options); CollectionFolder.SaveLibraryOptions(virtualFolderPath, options);
@@ -2909,7 +2940,7 @@ namespace Emby.Server.Implementations.Library
// // We can't validate protocol-based paths, so just allow them // // We can't validate protocol-based paths, so just allow them
// if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1) // if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1)
// { // {
// return Directory.Exists(path); // return _fileSystem.DirectoryExists(path);
// } // }
//} //}
@@ -2937,7 +2968,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(path)); throw new ArgumentNullException(nameof(path));
} }
if (!Directory.Exists(path)) if (!_fileSystem.DirectoryExists(path))
{ {
throw new FileNotFoundException("The path does not exist."); throw new FileNotFoundException("The path does not exist.");
} }
@@ -2950,11 +2981,11 @@ namespace Emby.Server.Implementations.Library
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
var shortcutFilename = Path.GetFileNameWithoutExtension(path); var shortcutFilename = _fileSystem.GetFileNameWithoutExtension(path);
var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension); var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
while (File.Exists(lnk)) while (_fileSystem.FileExists(lnk))
{ {
shortcutFilename += "1"; shortcutFilename += "1";
lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension); lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
@@ -3047,7 +3078,7 @@ namespace Emby.Server.Implementations.Library
var path = Path.Combine(rootFolderPath, name); var path = Path.Combine(rootFolderPath, name);
if (!Directory.Exists(path)) if (!_fileSystem.DirectoryExists(path))
{ {
throw new FileNotFoundException("The media folder does not exist"); throw new FileNotFoundException("The media folder does not exist");
} }
@@ -3056,7 +3087,7 @@ namespace Emby.Server.Implementations.Library
try try
{ {
Directory.Delete(path, true); _fileSystem.DeleteDirectory(path, true);
} }
finally finally
{ {
@@ -3119,7 +3150,7 @@ namespace Emby.Server.Implementations.Library
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
if (!Directory.Exists(virtualFolderPath)) if (!_fileSystem.DirectoryExists(virtualFolderPath))
{ {
throw new FileNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName)); throw new FileNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
} }
@@ -20,6 +20,7 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library namespace Emby.Server.Implementations.Library
@@ -35,6 +36,7 @@ namespace Emby.Server.Implementations.Library
private IMediaSourceProvider[] _providers; private IMediaSourceProvider[] _providers;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IUserDataManager _userDataManager; private readonly IUserDataManager _userDataManager;
private readonly ITimerFactory _timerFactory;
private readonly Func<IMediaEncoder> _mediaEncoder; private readonly Func<IMediaEncoder> _mediaEncoder;
private ILocalizationManager _localizationManager; private ILocalizationManager _localizationManager;
private IApplicationPaths _appPaths; private IApplicationPaths _appPaths;
@@ -49,6 +51,7 @@ namespace Emby.Server.Implementations.Library
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IFileSystem fileSystem, IFileSystem fileSystem,
IUserDataManager userDataManager, IUserDataManager userDataManager,
ITimerFactory timerFactory,
Func<IMediaEncoder> mediaEncoder) Func<IMediaEncoder> mediaEncoder)
{ {
_itemRepo = itemRepo; _itemRepo = itemRepo;
@@ -58,6 +61,7 @@ namespace Emby.Server.Implementations.Library
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_userDataManager = userDataManager; _userDataManager = userDataManager;
_timerFactory = timerFactory;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_localizationManager = localizationManager; _localizationManager = localizationManager;
_appPaths = applicationPaths; _appPaths = applicationPaths;
@@ -318,18 +322,18 @@ namespace Emby.Server.Implementations.Library
private string[] NormalizeLanguage(string language) private string[] NormalizeLanguage(string language)
{ {
if (language == null) if (language != null)
{ {
return Array.Empty<string>(); var culture = _localizationManager.FindLanguageInfo(language);
if (culture != null)
{
return culture.ThreeLetterISOLanguageNames;
}
return new string[] { language };
} }
var culture = _localizationManager.FindLanguageInfo(language); return Array.Empty<string>();
if (culture != null)
{
return culture.ThreeLetterISOLanguageNames;
}
return new string[] { language };
} }
private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
@@ -465,7 +469,7 @@ namespace Emby.Server.Implementations.Library
} }
// TODO: Don't hardcode this // TODO: Don't hardcode this
const bool isAudio = false; var isAudio = false;
try try
{ {
@@ -476,11 +480,9 @@ namespace Emby.Server.Implementations.Library
else else
{ {
// hack - these two values were taken from LiveTVMediaSourceProvider // hack - these two values were taken from LiveTVMediaSourceProvider
string cacheKey = request.OpenToken; var cacheKey = request.OpenToken;
await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths) await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths).AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken).ConfigureAwait(false);
.AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken)
.ConfigureAwait(false);
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -489,7 +491,6 @@ namespace Emby.Server.Implementations.Library
AddMediaInfo(mediaSource, isAudio); AddMediaInfo(mediaSource, isAudio);
} }
// TODO: @bond Fix
var json = _jsonSerializer.SerializeToString(mediaSource); var json = _jsonSerializer.SerializeToString(mediaSource);
_logger.LogInformation("Live stream opened: " + json); _logger.LogInformation("Live stream opened: " + json);
var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json); var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
@@ -666,7 +667,7 @@ namespace Emby.Server.Implementations.Library
if (cacheFilePath != null) if (cacheFilePath != null)
{ {
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath));
_jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath); _jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath);
//_logger.LogDebug("Saved media info to {0}", cacheFilePath); //_logger.LogDebug("Saved media info to {0}", cacheFilePath);
@@ -86,7 +86,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
.Where(i => !LibraryManager.IgnoreFile(i, args.Parent)) .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
.ToList(); .ToList();
return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false); if (isBooksCollectionType)
{
return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
return null;
} }
if (LibraryManager.IsAudioFile(args.Path, libraryOptions)) if (LibraryManager.IsAudioFile(args.Path, libraryOptions))
@@ -140,19 +145,36 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
private T FindAudio<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName) private T FindAudio<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName)
where T : MediaBrowser.Controller.Entities.Audio.Audio, new() where T : MediaBrowser.Controller.Entities.Audio.Audio, new()
{ {
var multiDiscFolders = new List<FileSystemMetadata>();
var libraryOptions = args.GetLibraryOptions();
var filesFromOtherItems = new List<FileSystemMetadata>();
// TODO: Allow GetMultiDiscMovie in here // TODO: Allow GetMultiDiscMovie in here
const bool supportsMultiVersion = false; var supportsMultiVersion = false;
var result = ResolveMultipleAudio<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ?? var result = ResolveMultipleAudio<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
new MultiItemResolverResult(); new MultiItemResolverResult();
if (result.Items.Count == 1) if (result.Items.Count == 1)
{ {
var videoPath = result.Items[0].Path;
// If we were supporting this we'd be checking filesFromOtherItems // If we were supporting this we'd be checking filesFromOtherItems
var item = (T)result.Items[0]; var hasOtherItems = false;
item.IsInMixedFolder = false;
item.Name = Path.GetFileName(item.ContainingFolderPath); if (!hasOtherItems)
return item; {
var item = (T)result.Items[0];
item.IsInMixedFolder = false;
item.Name = Path.GetFileName(item.ContainingFolderPath);
return item;
}
}
if (result.Items.Count == 0 && multiDiscFolders.Count > 0)
{
//return GetMultiDiscAudio<T>(multiDiscFolders, directoryService);
} }
return null; return null;
@@ -172,7 +194,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{ {
leftOver.Add(child); leftOver.Add(child);
} }
else if (!IsIgnored(child.Name)) else if (IsIgnored(child.Name))
{
}
else
{ {
files.Add(child); files.Add(child);
} }
@@ -410,7 +410,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
} }
// TODO: Allow GetMultiDiscMovie in here // TODO: Allow GetMultiDiscMovie in here
const bool supportsMultiVersion = true; var supportsMultiVersion = true;
var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ?? var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
new MultiItemResolverResult(); new MultiItemResolverResult();
@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
@@ -44,7 +43,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
var filename = Path.GetFileNameWithoutExtension(args.Path); var filename = Path.GetFileNameWithoutExtension(args.Path);
// Make sure the image doesn't belong to a video file // Make sure the image doesn't belong to a video file
var files = args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path)); var files = args.DirectoryService.GetFiles(_fileSystem.GetDirectoryName(args.Path));
var libraryOptions = args.GetLibraryOptions(); var libraryOptions = args.GetLibraryOptions();
foreach (var file in files) foreach (var file in files)
@@ -86,7 +85,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
return false; return false;
} }
private static readonly HashSet<string> IgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase) private static readonly string[] IgnoreFiles =
{ {
"folder", "folder",
"thumb", "thumb",
@@ -103,7 +102,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
{ {
var filename = Path.GetFileNameWithoutExtension(path) ?? string.Empty; var filename = Path.GetFileNameWithoutExtension(path) ?? string.Empty;
if (IgnoreFiles.Contains(filename)) if (IgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase))
{ {
return false; return false;
} }
@@ -113,7 +112,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
return false; return false;
} }
return imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.')); return imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.'), StringComparer.OrdinalIgnoreCase);
} }
} }
@@ -153,8 +153,16 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions(); var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions();
var episodeResolver = new Naming.TV.EpisodeResolver(namingOptions); var episodeResolver = new Naming.TV.EpisodeResolver(namingOptions);
bool? isNamed = null;
bool? isOptimistic = null;
var episodeInfo = episodeResolver.Resolve(fullName, false, true, false, fillExtendedInfo: false); if (!isTvContentType)
{
isNamed = true;
isOptimistic = false;
}
var episodeInfo = episodeResolver.Resolve(fullName, false, isNamed, isOptimistic, fillExtendedInfo: false);
if (episodeInfo != null && episodeInfo.EpisodeNumber.HasValue) if (episodeInfo != null && episodeInfo.EpisodeNumber.HasValue)
{ {
return true; return true;
@@ -99,12 +99,14 @@ namespace Emby.Server.Implementations.Library
if (!query.IncludeMedia) if (!query.IncludeMedia)
{ {
AddIfMissing(includeItemTypes, typeof(Genre).Name); AddIfMissing(includeItemTypes, typeof(Genre).Name);
AddIfMissing(includeItemTypes, typeof(GameGenre).Name);
AddIfMissing(includeItemTypes, typeof(MusicGenre).Name); AddIfMissing(includeItemTypes, typeof(MusicGenre).Name);
} }
} }
else else
{ {
AddIfMissing(excludeItemTypes, typeof(Genre).Name); AddIfMissing(excludeItemTypes, typeof(Genre).Name);
AddIfMissing(excludeItemTypes, typeof(GameGenre).Name);
AddIfMissing(excludeItemTypes, typeof(MusicGenre).Name); AddIfMissing(excludeItemTypes, typeof(MusicGenre).Name);
} }
@@ -23,6 +23,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Connect;
using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@@ -210,8 +211,11 @@ namespace Emby.Server.Implementations.Library
{ {
foreach (var user in users) foreach (var user in users)
{ {
user.Policy.IsAdministrator = true; if (!user.ConnectLinkType.HasValue || user.ConnectLinkType.Value == UserLinkType.LinkedUser)
UpdateUserPolicy(user, user.Policy, false); {
user.Policy.IsAdministrator = true;
UpdateUserPolicy(user, user.Policy, false);
}
} }
} }
} }
@@ -269,9 +273,13 @@ namespace Emby.Server.Implementations.Library
if (user != null) if (user != null)
{ {
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); // Authenticate using local credentials if not a guest
authenticationProvider = authResult.Item1; if (!user.ConnectLinkType.HasValue || user.ConnectLinkType.Value != UserLinkType.Guest)
success = authResult.Item2; {
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false);
authenticationProvider = authResult.Item1;
success = authResult.Item2;
}
} }
else else
{ {
@@ -314,14 +322,17 @@ namespace Emby.Server.Implementations.Library
throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name));
} }
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) if (user != null)
{ {
throw new SecurityException("Forbidden."); if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
} {
throw new SecurityException("Forbidden.");
}
if (!user.IsParentalScheduleAllowed()) if (!user.IsParentalScheduleAllowed())
{ {
throw new SecurityException("User is not allowed access at this time."); throw new SecurityException("User is not allowed access at this time.");
}
} }
// Update LastActivityDate and LastLoginDate, then save // Update LastActivityDate and LastLoginDate, then save
@@ -448,30 +459,30 @@ namespace Emby.Server.Implementations.Library
private void UpdateInvalidLoginAttemptCount(User user, int newValue) private void UpdateInvalidLoginAttemptCount(User user, int newValue)
{ {
if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0) if (user.Policy.InvalidLoginAttemptCount != newValue || newValue > 0)
{ {
return; user.Policy.InvalidLoginAttemptCount = newValue;
}
user.Policy.InvalidLoginAttemptCount = newValue; var maxCount = user.Policy.IsAdministrator ?
3 :
5;
var maxCount = user.Policy.IsAdministrator ? 3 : 5; var fireLockout = false;
var fireLockout = false; if (newValue >= maxCount)
{
//_logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture));
//user.Policy.IsDisabled = true;
if (newValue >= maxCount) //fireLockout = true;
{ }
_logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue);
user.Policy.IsDisabled = true;
fireLockout = true; UpdateUserPolicy(user, user.Policy, false);
}
UpdateUserPolicy(user, user.Policy, false); if (fireLockout)
{
if (fireLockout) UserLockedOut?.Invoke(this, new GenericEventArgs<User>(user));
{ }
UserLockedOut?.Invoke(this, new GenericEventArgs<User>(user));
} }
} }
@@ -546,6 +557,9 @@ namespace Emby.Server.Implementations.Library
LastActivityDate = user.LastActivityDate, LastActivityDate = user.LastActivityDate,
LastLoginDate = user.LastLoginDate, LastLoginDate = user.LastLoginDate,
Configuration = user.Configuration, Configuration = user.Configuration,
ConnectLinkType = user.ConnectLinkType,
ConnectUserId = user.ConnectUserId,
ConnectUserName = user.ConnectUserName,
ServerId = _appHost.SystemId, ServerId = _appHost.SystemId,
Policy = user.Policy Policy = user.Policy
}; };
@@ -804,6 +818,11 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(user)); 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); await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false);
UpdateUser(user); UpdateUser(user);
@@ -888,7 +907,7 @@ namespace Emby.Server.Implementations.Library
// Tuesday, 22 August 2006 06:30 AM // Tuesday, 22 August 2006 06:30 AM
text.AppendLine("The pin code will expire at " + localExpirationTime.ToString("f1", CultureInfo.CurrentCulture)); text.AppendLine("The pin code will expire at " + localExpirationTime.ToString("f1", CultureInfo.CurrentCulture));
File.WriteAllText(path, text.ToString(), Encoding.UTF8); _fileSystem.WriteAllText(path, text.ToString(), Encoding.UTF8);
var result = new PasswordPinCreationResult var result = new PasswordPinCreationResult
{ {
@@ -910,6 +929,11 @@ namespace Emby.Server.Implementations.Library
null : null :
GetUserByName(enteredUsername); 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; var action = ForgotPasswordAction.InNetworkRequired;
string pinFile = null; string pinFile = null;
DateTime? expirationDate = null; DateTime? expirationDate = null;
@@ -954,7 +978,10 @@ namespace Emby.Server.Implementations.Library
_lastPin = null; _lastPin = null;
_lastPasswordPinCreationResult = null; _lastPasswordPinCreationResult = null;
foreach (var user in Users) var users = Users.Where(i => !i.ConnectLinkType.HasValue || i.ConnectLinkType.Value != UserLinkType.Guest)
.ToList();
foreach (var user in users)
{ {
await ResetPassword(user).ConfigureAwait(false); await ResetPassword(user).ConfigureAwait(false);
@@ -1005,11 +1032,6 @@ namespace Emby.Server.Implementations.Library
{ {
var path = GetPolicyFilePath(user); var path = GetPolicyFilePath(user);
if (!File.Exists(path))
{
return GetDefaultPolicy(user);
}
try try
{ {
lock (_policySyncLock) lock (_policySyncLock)
@@ -1017,6 +1039,10 @@ namespace Emby.Server.Implementations.Library
return (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), path); return (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), path);
} }
} }
catch (FileNotFoundException)
{
return GetDefaultPolicy(user);
}
catch (IOException) catch (IOException)
{ {
return GetDefaultPolicy(user); return GetDefaultPolicy(user);
@@ -1056,7 +1082,7 @@ namespace Emby.Server.Implementations.Library
var path = GetPolicyFilePath(user); var path = GetPolicyFilePath(user);
Directory.CreateDirectory(Path.GetDirectoryName(path)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
lock (_policySyncLock) lock (_policySyncLock)
{ {
@@ -1105,11 +1131,6 @@ namespace Emby.Server.Implementations.Library
{ {
var path = GetConfigurationFilePath(user); var path = GetConfigurationFilePath(user);
if (!File.Exists(path))
{
return new UserConfiguration();
}
try try
{ {
lock (_configSyncLock) lock (_configSyncLock)
@@ -1117,6 +1138,10 @@ namespace Emby.Server.Implementations.Library
return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path); return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path);
} }
} }
catch (FileNotFoundException)
{
return new UserConfiguration();
}
catch (IOException) catch (IOException)
{ {
return new UserConfiguration(); return new UserConfiguration();
@@ -1152,7 +1177,7 @@ namespace Emby.Server.Implementations.Library
config = _jsonSerializer.DeserializeFromString<UserConfiguration>(json); config = _jsonSerializer.DeserializeFromString<UserConfiguration>(json);
} }
Directory.CreateDirectory(Path.GetDirectoryName(path)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
lock (_configSyncLock) lock (_configSyncLock)
{ {
@@ -1182,11 +1207,9 @@ namespace Emby.Server.Implementations.Library
_sessionManager = sessionManager; _sessionManager = sessionManager;
} }
public Task RunAsync() public void Run()
{ {
_userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated;
return Task.CompletedTask;
} }
private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e) private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
@@ -308,6 +308,9 @@ namespace Emby.Server.Implementations.Library
mediaTypes.Add(MediaType.Book); mediaTypes.Add(MediaType.Book);
mediaTypes.Add(MediaType.Audio); mediaTypes.Add(MediaType.Audio);
break; break;
case CollectionType.Games:
mediaTypes.Add(MediaType.Game);
break;
case CollectionType.Music: case CollectionType.Music:
mediaTypes.Add(MediaType.Audio); mediaTypes.Add(MediaType.Audio);
break; break;
@@ -333,6 +336,7 @@ namespace Emby.Server.Implementations.Library
typeof(Person).Name, typeof(Person).Name,
typeof(Studio).Name, typeof(Studio).Name,
typeof(Year).Name, typeof(Year).Name,
typeof(GameGenre).Name,
typeof(MusicGenre).Name, typeof(MusicGenre).Name,
typeof(Genre).Name typeof(Genre).Name
@@ -0,0 +1,45 @@
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);
}
}
}
@@ -0,0 +1,72 @@
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,5 +1,4 @@
using System; using System;
using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
@@ -42,7 +41,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{ {
Directory.CreateDirectory(Path.GetDirectoryName(targetFile)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(targetFile));
using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
{ {
@@ -78,7 +77,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
_logger.LogInformation("Opened recording stream from tuner provider"); _logger.LogInformation("Opened recording stream from tuner provider");
Directory.CreateDirectory(Path.GetDirectoryName(targetFile)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(targetFile));
using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
{ {
@@ -35,6 +35,8 @@ using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Reflection;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.System;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV namespace Emby.Server.Implementations.LiveTv.EmbyTV
@@ -58,11 +60,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IProviderManager _providerManager; private readonly IProviderManager _providerManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IProcessFactory _processFactory; private readonly IProcessFactory _processFactory;
private readonly ISystemEvents _systemEvents;
private readonly IAssemblyInfo _assemblyInfo; private readonly IAssemblyInfo _assemblyInfo;
private IMediaSourceManager _mediaSourceManager; private IMediaSourceManager _mediaSourceManager;
public static EmbyTV Current; public static EmbyTV Current;
public event EventHandler DataSourceChanged;
public event EventHandler<GenericEventArgs<TimerInfo>> TimerCreated; public event EventHandler<GenericEventArgs<TimerInfo>> TimerCreated;
public event EventHandler<GenericEventArgs<string>> TimerCancelled; public event EventHandler<GenericEventArgs<string>> TimerCancelled;
@@ -85,7 +89,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
ILibraryMonitor libraryMonitor, ILibraryMonitor libraryMonitor,
IProviderManager providerManager, IProviderManager providerManager,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IProcessFactory processFactory) ITimerFactory timerFactory,
IProcessFactory processFactory,
ISystemEvents systemEvents)
{ {
Current = this; Current = this;
@@ -99,6 +105,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_providerManager = providerManager; _providerManager = providerManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_processFactory = processFactory; _processFactory = processFactory;
_systemEvents = systemEvents;
_liveTvManager = (LiveTvManager)liveTvManager; _liveTvManager = (LiveTvManager)liveTvManager;
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_assemblyInfo = assemblyInfo; _assemblyInfo = assemblyInfo;
@@ -106,7 +113,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_streamHelper = streamHelper; _streamHelper = streamHelper;
_seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers")); _seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
_timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger); _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger, timerFactory);
_timerProvider.TimerFired += _timerProvider_TimerFired; _timerProvider.TimerFired += _timerProvider_TimerFired;
_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
@@ -120,13 +127,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
} }
} }
public async Task Start() public async void Start()
{ {
_timerProvider.RestartTimers(); _timerProvider.RestartTimers();
_systemEvents.Resume += _systemEvents_Resume;
await CreateRecordingFolders().ConfigureAwait(false); await CreateRecordingFolders().ConfigureAwait(false);
} }
private void _systemEvents_Resume(object sender, EventArgs e)
{
_timerProvider.RestartTimers();
}
private async void OnRecordingFoldersChanged() private async void OnRecordingFoldersChanged()
{ {
await CreateRecordingFolders().ConfigureAwait(false); await CreateRecordingFolders().ConfigureAwait(false);
@@ -271,7 +284,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
foreach (var timer in seriesTimers) foreach (var timer in seriesTimers)
{ {
UpdateTimersForSeriesTimer(timer, false, true); await UpdateTimersForSeriesTimer(timer, false, true).ConfigureAwait(false);
} }
} }
@@ -759,12 +772,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_timerProvider.AddOrUpdate(timer, false); _timerProvider.AddOrUpdate(timer, false);
} }
UpdateTimersForSeriesTimer(info, true, false); await UpdateTimersForSeriesTimer(info, true, false).ConfigureAwait(false);
return info.Id; return info.Id;
} }
public Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken) public async Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
{ {
var instance = _seriesTimerProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase)); var instance = _seriesTimerProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
@@ -788,10 +801,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_seriesTimerProvider.Update(instance); _seriesTimerProvider.Update(instance);
UpdateTimersForSeriesTimer(instance, true, true); await UpdateTimersForSeriesTimer(instance, true, true).ConfigureAwait(false);
} }
return Task.CompletedTask;
} }
public Task UpdateTimerAsync(TimerInfo updatedTimer, CancellationToken cancellationToken) public Task UpdateTimerAsync(TimerInfo updatedTimer, CancellationToken cancellationToken)
@@ -1122,8 +1133,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
IgnoreIndex = true IgnoreIndex = true
}; };
await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _config.CommonApplicationPaths) var isAudio = false;
.AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false); await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _config.CommonApplicationPaths).AddMediaInfoWithProbe(stream, isAudio, false, cancellationToken).ConfigureAwait(false);
return new List<MediaSourceInfo> return new List<MediaSourceInfo>
{ {
@@ -1138,12 +1149,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public Task RecordLiveStream(string id, CancellationToken cancellationToken) public Task RecordLiveStream(string id, CancellationToken cancellationToken)
{ {
return Task.CompletedTask; return Task.FromResult(0);
} }
public Task ResetTuner(string id, CancellationToken cancellationToken) public Task ResetTuner(string id, CancellationToken cancellationToken)
{ {
return Task.CompletedTask; return Task.FromResult(0);
} }
async void _timerProvider_TimerFired(object sender, GenericEventArgs<TimerInfo> e) async void _timerProvider_TimerFired(object sender, GenericEventArgs<TimerInfo> e)
@@ -1425,7 +1436,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
timer.RetryCount++; timer.RetryCount++;
_timerProvider.AddOrUpdate(timer); _timerProvider.AddOrUpdate(timer);
} }
else if (File.Exists(recordPath)) else if (_fileSystem.FileExists(recordPath))
{ {
timer.RecordingPath = recordPath; timer.RecordingPath = recordPath;
timer.Status = RecordingStatus.Completed; timer.Status = RecordingStatus.Completed;
@@ -1487,7 +1498,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
_logger.LogInformation("Triggering refresh on {path}", path); _logger.LogInformation("Triggering refresh on {path}", path);
var item = GetAffectedBaseItem(Path.GetDirectoryName(path)); var item = GetAffectedBaseItem(_fileSystem.GetDirectoryName(path));
if (item != null) if (item != null)
{ {
@@ -1498,8 +1509,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
RefreshPaths = new string[] RefreshPaths = new string[]
{ {
path, path,
Path.GetDirectoryName(path), _fileSystem.GetDirectoryName(path),
Path.GetDirectoryName(Path.GetDirectoryName(path)) _fileSystem.GetDirectoryName(_fileSystem.GetDirectoryName(path))
} }
}, RefreshPriority.High); }, RefreshPriority.High);
@@ -1510,13 +1521,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
BaseItem item = null; BaseItem item = null;
var parentPath = Path.GetDirectoryName(path); var parentPath = _fileSystem.GetDirectoryName(path);
while (item == null && !string.IsNullOrEmpty(path)) while (item == null && !string.IsNullOrEmpty(path))
{ {
item = _libraryManager.FindByPath(path, null); item = _libraryManager.FindByPath(path, null);
path = Path.GetDirectoryName(path); path = _fileSystem.GetDirectoryName(path);
} }
if (item != null) if (item != null)
@@ -1571,7 +1582,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
.Where(i => i.Status == RecordingStatus.Completed && !string.IsNullOrWhiteSpace(i.RecordingPath)) .Where(i => i.Status == RecordingStatus.Completed && !string.IsNullOrWhiteSpace(i.RecordingPath))
.Where(i => string.Equals(i.SeriesTimerId, seriesTimerId, StringComparison.OrdinalIgnoreCase)) .Where(i => string.Equals(i.SeriesTimerId, seriesTimerId, StringComparison.OrdinalIgnoreCase))
.OrderByDescending(i => i.EndDate) .OrderByDescending(i => i.EndDate)
.Where(i => File.Exists(i.RecordingPath)) .Where(i => _fileSystem.FileExists(i.RecordingPath))
.Skip(seriesTimer.KeepUpTo - 1) .Skip(seriesTimer.KeepUpTo - 1)
.ToList(); .ToList();
@@ -1593,7 +1604,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
DtoOptions = new DtoOptions(true) DtoOptions = new DtoOptions(true)
})) }))
.Where(i => i.IsFileProtocol && File.Exists(i.Path)) .Where(i => i.IsFileProtocol && _fileSystem.FileExists(i.Path))
.Skip(seriesTimer.KeepUpTo - 1) .Skip(seriesTimer.KeepUpTo - 1)
.ToList(); .ToList();
@@ -1674,7 +1685,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
while (FileExists(path, timerId)) while (FileExists(path, timerId))
{ {
var parent = Path.GetDirectoryName(originalPath); var parent = _fileSystem.GetDirectoryName(originalPath);
var name = Path.GetFileNameWithoutExtension(originalPath); var name = Path.GetFileNameWithoutExtension(originalPath);
name += " - " + index.ToString(CultureInfo.InvariantCulture); name += " - " + index.ToString(CultureInfo.InvariantCulture);
@@ -1687,7 +1698,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private bool FileExists(string path, string timerId) private bool FileExists(string path, string timerId)
{ {
if (File.Exists(path)) if (_fileSystem.FileExists(path))
{ {
return true; return true;
} }
@@ -1820,12 +1831,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return; return;
} }
var imageSavePath = Path.Combine(Path.GetDirectoryName(recordingPath), imageSaveFilenameWithoutExtension); var imageSavePath = Path.Combine(_fileSystem.GetDirectoryName(recordingPath), imageSaveFilenameWithoutExtension);
// preserve original image extension // preserve original image extension
imageSavePath = Path.ChangeExtension(imageSavePath, Path.GetExtension(image.Path)); imageSavePath = Path.ChangeExtension(imageSavePath, Path.GetExtension(image.Path));
File.Copy(image.Path, imageSavePath, true); _fileSystem.CopyFile(image.Path, imageSavePath, true);
} }
private async Task SaveRecordingImages(string recordingPath, LiveTvProgram program) private async Task SaveRecordingImages(string recordingPath, LiveTvProgram program)
@@ -1959,7 +1970,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
var nfoPath = Path.Combine(seriesPath, "tvshow.nfo"); var nfoPath = Path.Combine(seriesPath, "tvshow.nfo");
if (File.Exists(nfoPath)) if (_fileSystem.FileExists(nfoPath))
{ {
return; return;
} }
@@ -2021,7 +2032,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
var nfoPath = Path.ChangeExtension(recordingPath, ".nfo"); var nfoPath = Path.ChangeExtension(recordingPath, ".nfo");
if (File.Exists(nfoPath)) if (_fileSystem.FileExists(nfoPath))
{ {
return; return;
} }
@@ -2191,7 +2202,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (lockData) if (lockData)
{ {
writer.WriteElementString("lockdata", true.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); writer.WriteElementString("lockdata", true.ToString().ToLower());
} }
if (item.CriticRating.HasValue) if (item.CriticRating.HasValue)
@@ -2344,9 +2355,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
} }
} }
private void UpdateTimersForSeriesTimer(SeriesTimerInfo seriesTimer, bool updateTimerSettings, bool deleteInvalidTimers) private async Task UpdateTimersForSeriesTimer(SeriesTimerInfo seriesTimer, bool updateTimerSettings, bool deleteInvalidTimers)
{ {
var allTimers = GetTimersForSeries(seriesTimer).ToList(); var allTimers = GetTimersForSeries(seriesTimer)
.ToList();
var enabledTimersForSeries = new List<TimerInfo>(); var enabledTimersForSeries = new List<TimerInfo>();
@@ -2685,7 +2697,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var defaultFolder = RecordingPath; var defaultFolder = RecordingPath;
var defaultName = "Recordings"; var defaultName = "Recordings";
if (Directory.Exists(defaultFolder)) if (_fileSystem.DirectoryExists(defaultFolder))
{ {
list.Add(new VirtualFolderInfo list.Add(new VirtualFolderInfo
{ {
@@ -2695,7 +2707,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
} }
var customPath = GetConfiguration().MovieRecordingPath; var customPath = GetConfiguration().MovieRecordingPath;
if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && Directory.Exists(customPath)) if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && _fileSystem.DirectoryExists(customPath))
{ {
list.Add(new VirtualFolderInfo list.Add(new VirtualFolderInfo
{ {
@@ -2706,7 +2718,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
} }
customPath = GetConfiguration().SeriesRecordingPath; customPath = GetConfiguration().SeriesRecordingPath;
if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && Directory.Exists(customPath)) if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && _fileSystem.DirectoryExists(customPath))
{ {
list.Add(new VirtualFolderInfo 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) private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{ {
_targetPath = targetFile; _targetPath = targetFile;
Directory.CreateDirectory(Path.GetDirectoryName(targetFile)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(targetFile));
var process = _processFactory.Create(new ProcessOptions var process = _processFactory.Create(new ProcessOptions
{ {
@@ -105,7 +105,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_logger.LogInformation(commandLineLogMessage); _logger.LogInformation(commandLineLogMessage);
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt"); var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
Directory.CreateDirectory(Path.GetDirectoryName(logFilePath)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(logFilePath));
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
_logFileStream = _fileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true); _logFileStream = _fileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
@@ -175,6 +175,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
} }
var videoStream = mediaSource.VideoStream; var videoStream = mediaSource.VideoStream;
string videoDecoder = null;
if (!string.IsNullOrEmpty(videoDecoder))
{
inputModifier += " " + videoDecoder;
}
if (mediaSource.ReadAtNativeFramerate) if (mediaSource.ReadAtNativeFramerate)
{ {
@@ -1,13 +1,12 @@
using System.Threading.Tasks;
using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Plugins;
namespace Emby.Server.Implementations.LiveTv.EmbyTV namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
public class EntryPoint : IServerEntryPoint public class EntryPoint : IServerEntryPoint
{ {
public Task RunAsync() public void Run()
{ {
return EmbyTV.Current.Start(); EmbyTV.Current.Start();
} }
public void Dispose() public void Dispose()
@@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
} }
var file = _dataPath + ".json"; var file = _dataPath + ".json";
Directory.CreateDirectory(Path.GetDirectoryName(file)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(file));
lock (_fileDataLock) lock (_fileDataLock)
{ {
@@ -2,27 +2,29 @@ using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
public class TimerManager : ItemDataProvider<TimerInfo> public class TimerManager : ItemDataProvider<TimerInfo>
{ {
private readonly ConcurrentDictionary<string, Timer> _timers = new ConcurrentDictionary<string, Timer>(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary<string, ITimer> _timers = new ConcurrentDictionary<string, ITimer>(StringComparer.OrdinalIgnoreCase);
private readonly ILogger _logger; private readonly ILogger _logger;
public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired; public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
private readonly ITimerFactory _timerFactory;
public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1) public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1, ITimerFactory timerFactory)
: base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)) : base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{ {
_logger = logger1; _logger = logger1;
_timerFactory = timerFactory;
} }
public void RestartTimers() public void RestartTimers()
@@ -123,7 +125,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private void StartTimer(TimerInfo item, TimeSpan dueTime) private void StartTimer(TimerInfo item, TimeSpan dueTime)
{ {
var timer = new Timer(TimerCallback, item.Id, dueTime, TimeSpan.Zero); var timer = _timerFactory.Create(TimerCallback, item.Id, dueTime, TimeSpan.Zero);
if (_timers.TryAdd(item.Id, timer)) if (_timers.TryAdd(item.Id, timer))
{ {
@@ -17,7 +17,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.Listings namespace Jellyfin.Server.Implementations.LiveTv.Listings
{ {
public class XmlTvListingsProvider : IListingsProvider public class XmlTvListingsProvider : IListingsProvider
{ {
@@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
string cacheFilename = DateTime.UtcNow.DayOfYear.ToString(CultureInfo.InvariantCulture) + "-" + DateTime.UtcNow.Hour.ToString(CultureInfo.InvariantCulture) + ".xml"; string cacheFilename = DateTime.UtcNow.DayOfYear.ToString(CultureInfo.InvariantCulture) + "-" + DateTime.UtcNow.Hour.ToString(CultureInfo.InvariantCulture) + ".xml";
string cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename); string cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename);
if (File.Exists(cacheFile)) if (_fileSystem.FileExists(cacheFile))
{ {
return UnzipIfNeeded(path, cacheFile); return UnzipIfNeeded(path, cacheFile);
} }
@@ -83,9 +83,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}).ConfigureAwait(false); }).ConfigureAwait(false);
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFile));
File.Copy(tempFile, cacheFile, true); _fileSystem.CopyFile(tempFile, cacheFile, true);
return UnzipIfNeeded(path, cacheFile); return UnzipIfNeeded(path, cacheFile);
} }
@@ -122,10 +122,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private string ExtractFirstFileFromGz(string file) private string ExtractFirstFileFromGz(string file)
{ {
using (var stream = File.OpenRead(file)) using (var stream = _fileSystem.OpenRead(file))
{ {
string tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString()); string tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString());
Directory.CreateDirectory(tempFolder); _fileSystem.CreateDirectory(tempFolder);
_zipClient.ExtractFirstFileFromGz(stream, tempFolder, "data.xml"); _zipClient.ExtractFirstFileFromGz(stream, tempFolder, "data.xml");
@@ -135,10 +135,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private string ExtractGz(string file) private string ExtractGz(string file)
{ {
using (var stream = File.OpenRead(file)) using (var stream = _fileSystem.OpenRead(file))
{ {
string tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString()); string tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString());
Directory.CreateDirectory(tempFolder); _fileSystem.CreateDirectory(tempFolder);
_zipClient.ExtractAllFromGz(stream, tempFolder, true); _zipClient.ExtractAllFromGz(stream, tempFolder, true);
@@ -255,7 +255,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings) public Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings)
{ {
// Assume all urls are valid. check files for existence // Assume all urls are valid. check files for existence
if (!info.Path.StartsWith("http", StringComparison.OrdinalIgnoreCase) && !File.Exists(info.Path)) if (!info.Path.StartsWith("http", StringComparison.OrdinalIgnoreCase) && !_fileSystem.FileExists(info.Path))
{ {
throw new FileNotFoundException("Could not find the XmlTv file specified:", 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; var name = serviceName + externalId + InternalVersionNumber;
return _libraryManager.GetNewItemId(name.ToLowerInvariant(), typeof(LiveTvChannel)); return _libraryManager.GetNewItemId(name.ToLower(), typeof(LiveTvChannel));
} }
private const string ServiceName = "Emby"; private const string ServiceName = "Emby";
@@ -407,21 +407,21 @@ namespace Emby.Server.Implementations.LiveTv
{ {
var name = ServiceName + externalId + InternalVersionNumber; var name = ServiceName + externalId + InternalVersionNumber;
return name.ToLowerInvariant().GetMD5().ToString("N"); return name.ToLower().GetMD5().ToString("N");
} }
public Guid GetInternalSeriesTimerId(string externalId) public Guid GetInternalSeriesTimerId(string externalId)
{ {
var name = ServiceName + externalId + InternalVersionNumber; var name = ServiceName + externalId + InternalVersionNumber;
return name.ToLowerInvariant().GetMD5(); return name.ToLower().GetMD5();
} }
public Guid GetInternalProgramId(string externalId) public Guid GetInternalProgramId(string externalId)
{ {
var name = ServiceName + externalId + InternalVersionNumber; var name = ServiceName + externalId + InternalVersionNumber;
return _libraryManager.GetNewItemId(name.ToLowerInvariant(), typeof(LiveTvProgram)); return _libraryManager.GetNewItemId(name.ToLower(), typeof(LiveTvProgram));
} }
public async Task<TimerInfo> GetTimerInfo(TimerInfoDto dto, bool isNew, LiveTvManager liveTv, CancellationToken cancellationToken) public async Task<TimerInfo> GetTimerInfo(TimerInfoDto dto, bool isNew, LiveTvManager liveTv, CancellationToken cancellationToken)
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Emby.Server.Implementations.Library; using Emby.Server.Implementations.Library;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Progress; using MediaBrowser.Common.Progress;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
@@ -23,6 +24,7 @@ using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
@@ -46,6 +48,7 @@ namespace Emby.Server.Implementations.LiveTv
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ITaskManager _taskManager; private readonly ITaskManager _taskManager;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IProviderManager _providerManager;
private readonly Func<IChannelManager> _channelManager; private readonly Func<IChannelManager> _channelManager;
private readonly IDtoService _dtoService; private readonly IDtoService _dtoService;
@@ -53,7 +56,7 @@ namespace Emby.Server.Implementations.LiveTv
private readonly LiveTvDtoService _tvDtoService; private readonly LiveTvDtoService _tvDtoService;
private ILiveTvService[] _services = Array.Empty<ILiveTvService>(); private ILiveTvService[] _services = new ILiveTvService[] { };
private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>(); private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>();
private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>(); private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>();
@@ -82,6 +85,7 @@ namespace Emby.Server.Implementations.LiveTv
ITaskManager taskManager, ITaskManager taskManager,
ILocalizationManager localization, ILocalizationManager localization,
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IProviderManager providerManager,
IFileSystem fileSystem, IFileSystem fileSystem,
Func<IChannelManager> channelManager) Func<IChannelManager> channelManager)
{ {
@@ -93,6 +97,7 @@ namespace Emby.Server.Implementations.LiveTv
_taskManager = taskManager; _taskManager = taskManager;
_localization = localization; _localization = localization;
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_providerManager = providerManager;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_dtoService = dtoService; _dtoService = dtoService;
_userDataManager = userDataManager; _userDataManager = userDataManager;
@@ -127,6 +132,8 @@ namespace Emby.Server.Implementations.LiveTv
foreach (var service in _services) foreach (var service in _services)
{ {
service.DataSourceChanged += service_DataSourceChanged;
if (service is EmbyTV.EmbyTV embyTv) if (service is EmbyTV.EmbyTV embyTv)
{ {
embyTv.TimerCreated += EmbyTv_TimerCreated; embyTv.TimerCreated += EmbyTv_TimerCreated;
@@ -182,6 +189,14 @@ namespace Emby.Server.Implementations.LiveTv
return EmbyTV.EmbyTV.Current.DiscoverTuners(newDevicesOnly, cancellationToken); 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) public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
{ {
var user = query.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(query.UserId); var user = query.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(query.UserId);
@@ -2143,28 +2158,17 @@ namespace Emby.Server.Implementations.LiveTv
Dispose(true); Dispose(true);
} }
private bool _disposed = false; private bool _isDisposed = false;
/// <summary> /// <summary>
/// Releases unmanaged and - optionally - managed resources. /// Releases unmanaged and - optionally - managed resources.
/// </summary> /// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> /// <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) protected virtual void Dispose(bool dispose)
{ {
if (_disposed)
{
return;
}
if (dispose) if (dispose)
{ {
// TODO: Dispose stuff _isDisposed = true;
} }
_services = null;
_listingProviders = null;
_tunerHosts = null;
_disposed = true;
} }
private LiveTvServiceInfo[] GetServiceInfos() private LiveTvServiceInfo[] GetServiceInfos()
@@ -2465,8 +2469,7 @@ namespace Emby.Server.Implementations.LiveTv
.Where(i => i != null) .Where(i => i != null)
.Where(i => i.IsVisibleStandalone(user)) .Where(i => i.IsVisibleStandalone(user))
.SelectMany(i => _libraryManager.GetCollectionFolders(i)) .SelectMany(i => _libraryManager.GetCollectionFolders(i))
.GroupBy(x => x.Id) .DistinctBy(i => i.Id)
.Select(x => x.First())
.OrderBy(i => i.SortName) .OrderBy(i => i.SortName)
.ToList(); .ToList();
@@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
try try
{ {
Directory.CreateDirectory(Path.GetDirectoryName(channelCacheFile)); FileSystem.CreateDirectory(FileSystem.GetDirectoryName(channelCacheFile));
JsonSerializer.SerializeToFile(channels, channelCacheFile); JsonSerializer.SerializeToFile(channels, channelCacheFile);
} }
catch (IOException) catch (IOException)
@@ -354,8 +354,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
int? height = null; int? height = null;
bool isInterlaced = true; bool isInterlaced = true;
string videoCodec = null; string videoCodec = null;
string audioCodec = null;
int? videoBitrate = null; int? videoBitrate = null;
int? audioBitrate = null;
var isHd = channelInfo.IsHD ?? true; var isHd = channelInfo.IsHD ?? true;
@@ -425,17 +427,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
} }
} }
if (string.IsNullOrWhiteSpace(videoCodec)) if (channelInfo != null)
{ {
videoCodec = channelInfo.VideoCodec; if (string.IsNullOrWhiteSpace(videoCodec))
} {
string audioCodec = channelInfo.AudioCodec; videoCodec = channelInfo.VideoCodec;
}
audioCodec = channelInfo.AudioCodec;
if (!videoBitrate.HasValue) if (!videoBitrate.HasValue)
{ {
videoBitrate = isHd ? 15000000 : 2000000; videoBitrate = isHd ? 15000000 : 2000000;
}
audioBitrate = isHd ? 448000 : 192000;
} }
int? audioBitrate = isHd ? 448000 : 192000;
// normalize // normalize
if (string.Equals(videoCodec, "mpeg2", StringComparison.OrdinalIgnoreCase)) if (string.Equals(videoCodec, "mpeg2", StringComparison.OrdinalIgnoreCase))
@@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var uri = new Uri(mediaSource.Path); var uri = new Uri(mediaSource.Path);
var localPort = _networkManager.GetRandomUnusedUdpPort(); var localPort = _networkManager.GetRandomUnusedUdpPort();
Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath)); FileSystem.CreateDirectory(FileSystem.GetDirectoryName(TempFilePath));
Logger.LogInformation("Opening HDHR UDP Live stream from {host}", uri.Host); Logger.LogInformation("Opening HDHR UDP Live stream from {host}", uri.Host);
@@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
UserAgent = _appHost.ApplicationUserAgent UserAgent = _appHost.ApplicationUserAgent
}); });
} }
return Task.FromResult((Stream)File.OpenRead(url)); return Task.FromResult(_fileSystem.OpenRead(url));
} }
const string ExtInfPrefix = "#EXTINF:"; const string ExtInfPrefix = "#EXTINF:";
@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
@@ -36,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var url = mediaSource.Path; var url = mediaSource.Path;
Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath)); FileSystem.CreateDirectory(FileSystem.GetDirectoryName(TempFilePath));
var typeName = GetType().Name; var typeName = GetType().Name;
Logger.LogInformation("Opening " + typeName + " Live stream from {0}", url); Logger.LogInformation("Opening " + typeName + " Live stream from {0}", url);
@@ -94,7 +93,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token); StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
//OpenedMediaSource.Protocol = MediaProtocol.File; //OpenedMediaSource.Protocol = MediaProtocol.File;
//OpenedMediaSource.Path = tempFile; //OpenedMediaSource.Path = tempFile;

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