diff options
47 files changed, 533 insertions, 763 deletions
diff --git a/.github/ISSUE_TEMPLATE/media_playback.md b/.github/ISSUE_TEMPLATE/media_playback.md new file mode 100644 index 000000000..93af33fbf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/media_playback.md @@ -0,0 +1,32 @@ +--- +name: Media playback issue +about: Create a media playback issue report +title: '' +labels: mediaplayback +assignees: '' + +--- + +**Media Info of the file** +<!-- Use the Media Info tool (set to text format, download here: https://mediaarea.net/en/MediaInfo) or copy the info from the web ui for the file with the playback issue. --> + +**Logs** +<!-- Please paste any log message from during the playback issue, for example the ffmpeg command line can be very useful. --> + +**Stats for Nerds Screenshots** +<!-- If available, add screenshots of the stats for nerds screen to help show the issue problem. --> + +**Server System (please complete the following information):** + - OS: [e.g. Docker on Linux, Docker on Windows, Debian, Windows] + - Jellyfin Version: [e.g. 10.0.1] + - Hardware settings & device: [e.g. NVENC on GTX1060, VAAPI on Intel i7 8700K] + - Reverse proxy: [e.g. no, nginx, apache, etc.] + - Other hardware notes: [e.g. Media mounted in CIFS/SMB share, Media mounted from Google Drive] + +**Client System (please complete the following information):** + - Device: [e.g. Apple iPhone XS, Xbox One S, LG OLED55C8, Samsung Galaxy Note9, Custom HTPC] + - OS: [e.g. iOS, Android, Windows, macOS] + - Client: [e.g. Web/Browser, webOS, Android, Android TV, Electron] + - Browser (if Web client): [e.g. Firefox, Chrome, Safari] + - Client and Browser Version: [e.g. 10.3.4 and 68.0] + diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 81857e57c..c3fcea1e2 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -24,6 +24,7 @@ - [Lynxy](https://github.com/Lynxy) - [fasheng](https://github.com/fasheng) - [ploughpuff](https://github.com/ploughpuff) + - [pjeanjean](https://github.com/pjeanjean) # Emby Contributors diff --git a/Dockerfile b/Dockerfile index fb3bb633f..c971f1cc1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apt-get update \ COPY --from=ffmpeg / / COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.3 +ARG JELLYFIN_WEB_VERSION=10.3.5 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/Dockerfile.arm b/Dockerfile.arm index 2f43898fa..4847c726b 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -3,11 +3,6 @@ ARG DOTNET_VERSION=3.0 -FROM multiarch/qemu-user-static:x86_64-arm as qemu -FROM alpine as qemu_extract -COPY --from=qemu /usr/bin qemu-arm-static.tar.gz -RUN tar -xzvf qemu-arm-static.tar.gz - FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder WORKDIR /repo COPY . . @@ -21,8 +16,9 @@ RUN bash -c "source deployment/common.build.sh && \ build_jellyfin Jellyfin.Server Release linux-arm /jellyfin" +FROM multiarch/qemu-user-static:x86_64-arm as qemu FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm32v7 -COPY --from=qemu_extract qemu-arm-static /usr/bin +COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \ && rm -rf /var/lib/apt/lists/* \ @@ -30,7 +26,7 @@ RUN apt-get update \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.3 +ARG JELLYFIN_WEB_VERSION=10.3.5 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 5aa29edd5..a26cfc7b3 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -3,12 +3,6 @@ ARG DOTNET_VERSION=3.0 -FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu -FROM alpine as qemu_extract -COPY --from=qemu /usr/bin qemu-aarch64-static.tar.gz -RUN tar -xzvf qemu-aarch64-static.tar.gz - - FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder WORKDIR /repo COPY . . @@ -22,8 +16,9 @@ RUN bash -c "source deployment/common.build.sh && \ build_jellyfin Jellyfin.Server Release linux-arm64 /jellyfin" +FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm64v8 -COPY --from=qemu_extract qemu-aarch64-static /usr/bin +COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \ && rm -rf /var/lib/apt/lists/* \ @@ -31,7 +26,7 @@ RUN apt-get update \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.3 +ARG JELLYFIN_WEB_VERSION=10.3.5 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index 6e05eb795..9e2a4950f 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -23,7 +23,7 @@ <!-- Code analysers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.2" /> + <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.3" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" /> </ItemGroup> diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 190e4d55c..0530a251c 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -3,12 +3,10 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -29,31 +27,39 @@ namespace Emby.Server.Implementations.Activity { public class ActivityLogEntryPoint : IServerEntryPoint { + private readonly ILogger _logger; private readonly IInstallationManager _installationManager; private readonly ISessionManager _sessionManager; private readonly ITaskManager _taskManager; private readonly IActivityManager _activityManager; private readonly ILocalizationManager _localization; - private readonly ILibraryManager _libraryManager; private readonly ISubtitleManager _subManager; private readonly IUserManager _userManager; - private readonly IServerConfigurationManager _config; private readonly IServerApplicationHost _appHost; private readonly IDeviceManager _deviceManager; - public ActivityLogEntryPoint(ISessionManager sessionManager, IDeviceManager deviceManager, ITaskManager taskManager, IActivityManager activityManager, ILocalizationManager localization, IInstallationManager installationManager, ILibraryManager libraryManager, ISubtitleManager subManager, IUserManager userManager, IServerConfigurationManager config, IServerApplicationHost appHost) + public ActivityLogEntryPoint( + ILogger<ActivityLogEntryPoint> logger, + ISessionManager sessionManager, + IDeviceManager deviceManager, + ITaskManager taskManager, + IActivityManager activityManager, + ILocalizationManager localization, + IInstallationManager installationManager, + ISubtitleManager subManager, + IUserManager userManager, + IServerApplicationHost appHost) { + _logger = logger; _sessionManager = sessionManager; + _deviceManager = deviceManager; _taskManager = taskManager; _activityManager = activityManager; _localization = localization; _installationManager = installationManager; - _libraryManager = libraryManager; _subManager = subManager; _userManager = userManager; - _config = config; _appHost = appHost; - _deviceManager = deviceManager; } public Task RunAsync() @@ -122,7 +128,7 @@ namespace Emby.Server.Implementations.Activity if (item == null) { - //_logger.LogWarning("PlaybackStopped reported with null media info."); + _logger.LogWarning("PlaybackStopped reported with null media info."); return; } @@ -153,7 +159,7 @@ namespace Emby.Server.Implementations.Activity if (item == null) { - //_logger.LogWarning("PlaybackStart reported with null media info."); + _logger.LogWarning("PlaybackStart reported with null media info."); return; } @@ -201,6 +207,7 @@ namespace Emby.Server.Implementations.Activity { return NotificationType.AudioPlayback.ToString(); } + if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { return NotificationType.VideoPlayback.ToString(); @@ -215,6 +222,7 @@ namespace Emby.Server.Implementations.Activity { return NotificationType.AudioPlaybackStopped.ToString(); } + if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { return NotificationType.VideoPlaybackStopped.ToString(); @@ -403,6 +411,7 @@ namespace Emby.Server.Implementations.Activity { vals.Add(e.Result.ErrorMessage); } + if (!string.IsNullOrEmpty(e.Result.LongErrorMessage)) { vals.Add(e.Result.LongErrorMessage); @@ -412,7 +421,7 @@ namespace Emby.Server.Implementations.Activity { Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name), Type = NotificationType.TaskFailed.ToString(), - Overview = string.Join(Environment.NewLine, vals.ToArray()), + Overview = string.Join(Environment.NewLine, vals), ShortOverview = runningTime, Severity = LogLevel.Error }); @@ -489,6 +498,7 @@ namespace Emby.Server.Implementations.Activity { values.Add(CreateValueString(span.Hours, "hour")); } + // Number of minutes if (span.Minutes >= 1) { @@ -512,6 +522,7 @@ namespace Emby.Server.Implementations.Activity builder.Append(values[i]); } + // Return result return builder.ToString(); } diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 53bc85b28..62cc6ec47 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net; +using System.Net.Http; using System.Reflection; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; @@ -231,11 +232,6 @@ namespace Emby.Server.Implementations /// <value>The server configuration manager.</value> public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager; - protected virtual IResourceFileManager CreateResourceFileManager() - { - return new ResourceFileManager(HttpResultFactory, LoggerFactory, FileSystemManager); - } - /// <summary> /// Gets or sets the user manager. /// </summary> @@ -826,10 +822,10 @@ namespace Emby.Server.Implementations DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager); serviceCollection.AddSingleton(DtoService); - ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager); + ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager); serviceCollection.AddSingleton(ChannelManager); - SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager); + SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, this, AuthenticationRepository, DeviceManager, MediaSourceManager); serviceCollection.AddSingleton(SessionManager); serviceCollection.AddSingleton<IDlnaManager>( @@ -886,7 +882,7 @@ namespace Emby.Server.Implementations SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory); serviceCollection.AddSingleton(SubtitleEncoder); - serviceCollection.AddSingleton(CreateResourceFileManager()); + serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager)); displayPreferencesRepo.Initialize(); @@ -1014,7 +1010,6 @@ namespace Emby.Server.Implementations Video.LiveTvManager = LiveTvManager; Folder.UserViewManager = UserViewManager; UserView.TVSeriesManager = TVSeriesManager; - UserView.PlaylistManager = PlaylistManager; UserView.CollectionManager = CollectionManager; BaseItem.MediaSourceManager = MediaSourceManager; CollectionFolder.XmlSerializer = XmlSerializer; @@ -1539,12 +1534,12 @@ namespace Emby.Server.Implementations LogErrorResponseBody = false, LogErrors = false, LogRequest = false, - TimeoutMs = 10000, BufferContent = false, CancellationToken = cancellationToken }).ConfigureAwait(false)) { - return GetWanApiUrl(response.ReadToEnd().Trim()); + string res = await response.ReadToEndAsync().ConfigureAwait(false); + return GetWanApiUrl(res.Trim()); } } catch (Exception ex) @@ -1697,15 +1692,15 @@ namespace Emby.Server.Implementations LogErrorResponseBody = false, LogErrors = LogPing, LogRequest = LogPing, - TimeoutMs = 5000, BufferContent = false, CancellationToken = cancellationToken - }, "POST").ConfigureAwait(false)) + + }, HttpMethod.Post).ConfigureAwait(false)) { using (var reader = new StreamReader(response.Content)) { - var result = reader.ReadToEnd(); + var result = await reader.ReadToEndAsync().ConfigureAwait(false); var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase); _validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid); diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 7e50650d7..e9961e8bd 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; @@ -20,7 +19,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; @@ -40,11 +38,8 @@ namespace Emby.Server.Implementations.Channels private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; - private readonly IHttpClient _httpClient; private readonly IProviderManager _providerManager; - private readonly ILocalizationManager _localization; - public ChannelManager( IUserManager userManager, IDtoService dtoService, @@ -54,8 +49,6 @@ namespace Emby.Server.Implementations.Channels IFileSystem fileSystem, IUserDataManager userDataManager, IJsonSerializer jsonSerializer, - ILocalizationManager localization, - IHttpClient httpClient, IProviderManager providerManager) { _userManager = userManager; @@ -66,8 +59,6 @@ namespace Emby.Server.Implementations.Channels _fileSystem = fileSystem; _userDataManager = userDataManager; _jsonSerializer = jsonSerializer; - _localization = localization; - _httpClient = httpClient; _providerManager = providerManager; } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 7b28a22a8..2f1b60be9 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -89,14 +89,11 @@ namespace Emby.Server.Implementations.Dto var channelTuples = new List<Tuple<BaseItemDto, LiveTvChannel>>(); var index = 0; - var allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList(); - foreach (var item in items) { - var dto = GetBaseItemDtoInternal(item, options, allCollectionFolders, user, owner); + var dto = GetBaseItemDtoInternal(item, options, user, owner); - var tvChannel = item as LiveTvChannel; - if (tvChannel != null) + if (item is LiveTvChannel tvChannel) { channelTuples.Add(new Tuple<BaseItemDto, LiveTvChannel>(dto, tvChannel)); } @@ -105,9 +102,7 @@ namespace Emby.Server.Implementations.Dto programTuples.Add(new Tuple<BaseItem, BaseItemDto>(item, dto)); } - var byName = item as IItemByName; - - if (byName != null) + if (item is IItemByName byName) { if (options.ContainsField(ItemFields.ItemCounts)) { @@ -130,8 +125,7 @@ namespace Emby.Server.Implementations.Dto if (programTuples.Count > 0) { - var task = _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user); - Task.WaitAll(task); + _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult(); } if (channelTuples.Count > 0) @@ -144,8 +138,7 @@ namespace Emby.Server.Implementations.Dto public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) { - var allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList(); - var dto = GetBaseItemDtoInternal(item, options, allCollectionFolders, user, owner); + var dto = GetBaseItemDtoInternal(item, options, user, owner); var tvChannel = item as LiveTvChannel; if (tvChannel != null) { @@ -188,7 +181,7 @@ namespace Emby.Server.Implementations.Dto }); } - private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, List<Folder> allCollectionFolders, User user = null, BaseItem owner = null) + private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) { var dto = new BaseItemDto { @@ -312,6 +305,7 @@ namespace Emby.Server.Implementations.Dto { path = path.TrimStart('.'); } + if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparer.OrdinalIgnoreCase)) { fileExtensionContainer = path; @@ -325,8 +319,7 @@ namespace Emby.Server.Implementations.Dto public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, User user = null) { - var allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList(); - var dto = GetBaseItemDtoInternal(item, options, allCollectionFolders, user); + var dto = GetBaseItemDtoInternal(item, options, user); if (taggedItems != null && options.ContainsField(ItemFields.ItemCounts)) { @@ -1051,14 +1044,15 @@ namespace Emby.Server.Implementations.Dto } else { - mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, item.Id.ToString("N"), StringComparison.OrdinalIgnoreCase)) + string id = item.Id.ToString("N"); + mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)) .SelectMany(i => i.MediaStreams) .ToArray(); } } else { - mediaStreams = _mediaSourceManager().GetStaticMediaSources(item, true).First().MediaStreams.ToArray(); + mediaStreams = _mediaSourceManager().GetStaticMediaSources(item, true)[0].MediaStreams.ToArray(); } dto.MediaStreams = mediaStreams; diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index d4e17c42a..49015a07e 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -31,10 +31,9 @@ <PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" /> - <PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" /> - <PackageReference Include="sharpcompress" Version="0.22.0" /> + <PackageReference Include="ServiceStack.Text.Core" Version="5.5.0" /> + <PackageReference Include="sharpcompress" Version="0.23.0" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="1.0.0" /> - <PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" /> </ItemGroup> <ItemGroup> @@ -52,7 +51,7 @@ <!-- Code analysers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.2" /> + <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.3" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" /> </ItemGroup> diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs deleted file mode 100644 index f747b01b9..000000000 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Net.Http; - -namespace Emby.Server.Implementations.HttpClientManager -{ - /// <summary> - /// Class HttpClientInfo - /// </summary> - public class HttpClientInfo - { - /// <summary> - /// Gets or sets the last timeout. - /// </summary> - /// <value>The last timeout.</value> - public DateTime LastTimeout { get; set; } - public HttpClient HttpClient { get; set; } - } -} diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 1bebdd163..b82d55d0e 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Net; -using System.Net.Cache; +using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -55,12 +54,13 @@ namespace Emby.Server.Implementations.HttpClientManager { throw new ArgumentNullException(nameof(appPaths)); } + if (loggerFactory == null) { throw new ArgumentNullException(nameof(loggerFactory)); } - _logger = loggerFactory.CreateLogger("HttpClient"); + _logger = loggerFactory.CreateLogger(nameof(HttpClientManager)); _fileSystem = fileSystem; _appPaths = appPaths; _defaultUserAgentFn = defaultUserAgentFn; @@ -74,27 +74,26 @@ namespace Emby.Server.Implementations.HttpClientManager /// DON'T dispose it after use. /// </summary> /// <value>The HTTP clients.</value> - private readonly ConcurrentDictionary<string, HttpClientInfo> _httpClients = new ConcurrentDictionary<string, HttpClientInfo>(); + private readonly ConcurrentDictionary<string, HttpClient> _httpClients = new ConcurrentDictionary<string, HttpClient>(); /// <summary> /// Gets /// </summary> - /// <param name="host">The host.</param> + /// <param name="url">The host.</param> /// <param name="enableHttpCompression">if set to <c>true</c> [enable HTTP compression].</param> /// <returns>HttpClient.</returns> /// <exception cref="ArgumentNullException">host</exception> - private HttpClientInfo GetHttpClient(string host, bool enableHttpCompression) + private HttpClient GetHttpClient(string url, bool enableHttpCompression) { - if (string.IsNullOrEmpty(host)) - { - throw new ArgumentNullException(nameof(host)); - } - - var key = host + enableHttpCompression; + var key = GetHostFromUrl(url) + enableHttpCompression; if (!_httpClients.TryGetValue(key, out var client)) { - client = new HttpClientInfo(); + + client = new HttpClient() + { + BaseAddress = new Uri(url) + }; _httpClients.TryAdd(key, client); } @@ -102,110 +101,87 @@ namespace Emby.Server.Implementations.HttpClientManager return client; } - private WebRequest GetRequest(HttpRequestOptions options, string method) + private HttpRequestMessage GetRequestMessage(HttpRequestOptions options, HttpMethod method) { string url = options.Url; - var uriAddress = new Uri(url); string userInfo = uriAddress.UserInfo; if (!string.IsNullOrWhiteSpace(userInfo)) { - _logger.LogInformation("Found userInfo in url: {0} ... url: {1}", userInfo, url); + _logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url); url = url.Replace(userInfo + "@", string.Empty); } - var request = WebRequest.Create(url); + var request = new HttpRequestMessage(method, url); - if (request is HttpWebRequest httpWebRequest) - { - AddRequestHeaders(httpWebRequest, options); + AddRequestHeaders(request, options); - if (options.EnableHttpCompression) + if (options.EnableHttpCompression) + { + if (options.DecompressionMethod.HasValue + && options.DecompressionMethod.Value == CompressionMethod.Gzip) { - httpWebRequest.AutomaticDecompression = DecompressionMethods.Deflate; - if (options.DecompressionMethod.HasValue - && options.DecompressionMethod.Value == CompressionMethod.Gzip) - { - httpWebRequest.AutomaticDecompression = DecompressionMethods.GZip; - } + request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" }); } else { - httpWebRequest.AutomaticDecompression = DecompressionMethods.None; + request.Headers.Add(HeaderNames.AcceptEncoding, "deflate"); } + } - httpWebRequest.KeepAlive = options.EnableKeepAlive; + if (options.EnableKeepAlive) + { + request.Headers.Add(HeaderNames.Connection, "Keep-Alive"); + } - if (!string.IsNullOrEmpty(options.Host)) - { - httpWebRequest.Host = options.Host; - } + if (!string.IsNullOrEmpty(options.Host)) + { + request.Headers.Add(HeaderNames.Host, options.Host); + } - if (!string.IsNullOrEmpty(options.Referer)) - { - httpWebRequest.Referer = options.Referer; - } + if (!string.IsNullOrEmpty(options.Referer)) + { + request.Headers.Add(HeaderNames.Referer, options.Referer); } - request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); + //request.Headers.Add(HeaderNames.CacheControl, "no-cache"); - request.Method = method; - request.Timeout = options.TimeoutMs; + //request.Headers.Add(HeaderNames., options.TimeoutMs; + /* if (!string.IsNullOrWhiteSpace(userInfo)) { var parts = userInfo.Split(':'); if (parts.Length == 2) { - request.Credentials = GetCredential(url, parts[0], parts[1]); - // TODO: .net core ?? - request.PreAuthenticate = true; + request.Headers.Add(HeaderNames., GetCredential(url, parts[0], parts[1]); } } + */ return request; } - private static CredentialCache GetCredential(string url, string username, string password) - { - //ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3; - var credentialCache = new CredentialCache(); - credentialCache.Add(new Uri(url), "Basic", new NetworkCredential(username, password)); - return credentialCache; - } - - private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options) + private void AddRequestHeaders(HttpRequestMessage request, HttpRequestOptions options) { var hasUserAgent = false; foreach (var header in options.RequestHeaders) { - if (string.Equals(header.Key, HeaderNames.Accept, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase)) { - request.Accept = header.Value; - } - else if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase)) - { - SetUserAgent(request, header.Value); hasUserAgent = true; } - else - { - request.Headers.Set(header.Key, header.Value); - } + + request.Headers.Add(header.Key, header.Value); } if (!hasUserAgent && options.EnableDefaultUserAgent) { - SetUserAgent(request, _defaultUserAgentFn()); + request.Headers.Add(HeaderNames.UserAgent, _defaultUserAgentFn()); } } - private static void SetUserAgent(HttpWebRequest request, string userAgent) - { - request.UserAgent = userAgent; - } - /// <summary> /// Gets the response internal. /// </summary> @@ -213,7 +189,7 @@ namespace Emby.Server.Implementations.HttpClientManager /// <returns>Task{HttpResponseInfo}.</returns> public Task<HttpResponseInfo> GetResponse(HttpRequestOptions options) { - return SendAsync(options, "GET"); + return SendAsync(options, HttpMethod.Get); } /// <summary> @@ -235,7 +211,21 @@ namespace Emby.Server.Implementations.HttpClientManager /// <returns>Task{HttpResponseInfo}.</returns> /// <exception cref="HttpException"> /// </exception> - public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod) + public Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod) + { + var httpMethod2 = GetHttpMethod(httpMethod); + return SendAsync(options, httpMethod2); + } + + /// <summary> + /// send as an asynchronous operation. + /// </summary> + /// <param name="options">The options.</param> + /// <param name="httpMethod">The HTTP method.</param> + /// <returns>Task{HttpResponseInfo}.</returns> + /// <exception cref="HttpException"> + /// </exception> + public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, HttpMethod httpMethod) { if (options.CacheMode == CacheMode.None) { @@ -263,6 +253,40 @@ namespace Emby.Server.Implementations.HttpClientManager return response; } + private HttpMethod GetHttpMethod(string httpMethod) + { + if (httpMethod.Equals("DELETE", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Delete; + } + else if (httpMethod.Equals("GET", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Get; + } + else if (httpMethod.Equals("HEAD", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Head; + } + else if (httpMethod.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Options; + } + else if (httpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Post; + } + else if (httpMethod.Equals("PUT", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Put; + } + else if (httpMethod.Equals("TRACE", StringComparison.OrdinalIgnoreCase)) + { + return HttpMethod.Trace; + } + + throw new ArgumentException("Invalid HTTP method", nameof(httpMethod)); + } + private HttpResponseInfo GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url) { if (File.Exists(responseCachePath) @@ -294,31 +318,23 @@ namespace Emby.Server.Implementations.HttpClientManager } } - private async Task<HttpResponseInfo> SendAsyncInternal(HttpRequestOptions options, string httpMethod) + private async Task<HttpResponseInfo> SendAsyncInternal(HttpRequestOptions options, HttpMethod httpMethod) { ValidateParams(options); options.CancellationToken.ThrowIfCancellationRequested(); - var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression); + var client = GetHttpClient(options.Url, options.EnableHttpCompression); - if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < TimeoutSeconds) - { - throw new HttpException(string.Format("Cancelling connection to {0} due to a previous timeout.", options.Url)) - { - IsTimedOut = true - }; - } - - var httpWebRequest = GetRequest(options, httpMethod); + var httpWebRequest = GetRequestMessage(options, httpMethod); if (options.RequestContentBytes != null || !string.IsNullOrEmpty(options.RequestContent) || - string.Equals(httpMethod, "post", StringComparison.OrdinalIgnoreCase)) + httpMethod == HttpMethod.Post) { try { - var bytes = options.RequestContentBytes ?? Encoding.UTF8.GetBytes(options.RequestContent ?? string.Empty); + httpWebRequest.Content = new StringContent(Encoding.UTF8.GetString(options.RequestContentBytes) ?? options.RequestContent ?? string.Empty); var contentType = options.RequestContentType ?? "application/x-www-form-urlencoded"; @@ -327,8 +343,8 @@ namespace Emby.Server.Implementations.HttpClientManager contentType = contentType.TrimEnd(';') + "; charset=\"utf-8\""; } - httpWebRequest.ContentType = contentType; - (await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length); + httpWebRequest.Headers.Add(HeaderNames.ContentType, contentType); + await client.SendAsync(httpWebRequest).ConfigureAwait(false); } catch (Exception ex) { @@ -336,143 +352,96 @@ namespace Emby.Server.Implementations.HttpClientManager } } - if (options.ResourcePool != null) - { - await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false); - } - - if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < TimeoutSeconds) - { - options.ResourcePool?.Release(); - - throw new HttpException($"Connection to {options.Url} timed out") { IsTimedOut = true }; - } - if (options.LogRequest) { - if (options.LogRequestAsDebug) - { - _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url); - } - else - { - _logger.LogInformation("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url); - } + _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToString(), options.Url); } try { options.CancellationToken.ThrowIfCancellationRequested(); - if (!options.BufferContent) + /*if (!options.BufferContent) { - var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false); + var response = await client.HttpClient.SendAsync(httpWebRequest).ConfigureAwait(false); - var httpResponse = (HttpWebResponse)response; - - EnsureSuccessStatusCode(client, httpResponse, options); + await EnsureSuccessStatusCode(client, response, options).ConfigureAwait(false); options.CancellationToken.ThrowIfCancellationRequested(); - return GetResponseInfo(httpResponse, httpResponse.GetResponseStream(), GetContentLength(httpResponse), httpResponse); - } + return GetResponseInfo(response, await response.Content.ReadAsStreamAsync().ConfigureAwait(false), response.Content.Headers.ContentLength, response); + }*/ - using (var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false)) + using (var response = await client.SendAsync(httpWebRequest).ConfigureAwait(false)) { - var httpResponse = (HttpWebResponse)response; - - EnsureSuccessStatusCode(client, httpResponse, options); + await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); options.CancellationToken.ThrowIfCancellationRequested(); - using (var stream = httpResponse.GetResponseStream()) + using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) { var memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream).ConfigureAwait(false); - memoryStream.Position = 0; - return GetResponseInfo(httpResponse, memoryStream, memoryStream.Length, null); + return GetResponseInfo(response, memoryStream, memoryStream.Length, null); } } } catch (OperationCanceledException ex) { - throw GetCancellationException(options, client, options.CancellationToken, ex); - } - catch (Exception ex) - { - throw GetException(ex, options, client); - } - finally - { - options.ResourcePool?.Release(); + throw GetCancellationException(options, options.CancellationToken, ex); } } - private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, Stream content, long? contentLength, IDisposable disposable) + private HttpResponseInfo GetResponseInfo(HttpResponseMessage httpResponse, Stream content, long? contentLength, IDisposable disposable) { var responseInfo = new HttpResponseInfo(disposable) { Content = content, StatusCode = httpResponse.StatusCode, - ContentType = httpResponse.ContentType, + ContentType = httpResponse.Content.Headers.ContentType?.MediaType, ContentLength = contentLength, - ResponseUrl = httpResponse.ResponseUri.ToString() + ResponseUrl = httpResponse.Content.Headers.ContentLocation?.ToString() }; if (httpResponse.Headers != null) { - SetHeaders(httpResponse.Headers, responseInfo); + SetHeaders(httpResponse.Content.Headers, responseInfo); } return responseInfo; } - private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, string tempFile, long? contentLength) + private HttpResponseInfo GetResponseInfo(HttpResponseMessage httpResponse, string tempFile, long? contentLength) { var responseInfo = new HttpResponseInfo { TempFilePath = tempFile, StatusCode = httpResponse.StatusCode, - ContentType = httpResponse.ContentType, + ContentType = httpResponse.Content.Headers.ContentType?.MediaType, ContentLength = contentLength }; if (httpResponse.Headers != null) { - SetHeaders(httpResponse.Headers, responseInfo); + SetHeaders(httpResponse.Content.Headers, responseInfo); } return responseInfo; } - private static void SetHeaders(WebHeaderCollection headers, HttpResponseInfo responseInfo) + private static void SetHeaders(HttpContentHeaders headers, HttpResponseInfo responseInfo) { - foreach (var key in headers.AllKeys) + foreach (var key in headers) { - responseInfo.Headers[key] = headers[key]; + responseInfo.Headers[key.Key] = string.Join(", ", key.Value); } } public Task<HttpResponseInfo> Post(HttpRequestOptions options) { - return SendAsync(options, "POST"); - } - - /// <summary> - /// Performs a POST request - /// </summary> - /// <param name="options">The options.</param> - /// <param name="postData">Params to add to the POST data.</param> - /// <returns>stream on success, null on failure</returns> - public async Task<Stream> Post(HttpRequestOptions options, Dictionary<string, string> postData) - { - options.SetPostData(postData); - - var response = await Post(options).ConfigureAwait(false); - - return response.Content; + return SendAsync(options, HttpMethod.Post); } /// <summary> @@ -482,9 +451,10 @@ namespace Emby.Server.Implementations.HttpClientManager /// <returns>Task{System.String}.</returns> public async Task<string> GetTempFile(HttpRequestOptions options) { - var response = await GetTempFileResponse(options).ConfigureAwait(false); - - return response.TempFilePath; + using (var response = await GetTempFileResponse(options).ConfigureAwait(false)) + { + return response.TempFilePath; + } } public async Task<HttpResponseInfo> GetTempFileResponse(HttpRequestOptions options) @@ -502,44 +472,28 @@ namespace Emby.Server.Implementations.HttpClientManager options.CancellationToken.ThrowIfCancellationRequested(); - var httpWebRequest = GetRequest(options, "GET"); - - if (options.ResourcePool != null) - { - await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false); - } + var httpWebRequest = GetRequestMessage(options, HttpMethod.Get); options.Progress.Report(0); if (options.LogRequest) { - if (options.LogRequestAsDebug) - { - _logger.LogDebug("HttpClientManager.GetTempFileResponse url: {0}", options.Url); - } - else - { - _logger.LogInformation("HttpClientManager.GetTempFileResponse url: {0}", options.Url); - } + _logger.LogDebug("HttpClientManager.GetTempFileResponse url: {0}", options.Url); } - var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression); + var client = GetHttpClient(options.Url, options.EnableHttpCompression); try { options.CancellationToken.ThrowIfCancellationRequested(); - using (var response = await httpWebRequest.GetResponseAsync().ConfigureAwait(false)) + using (var response = (await client.SendAsync(httpWebRequest).ConfigureAwait(false))) { - var httpResponse = (HttpWebResponse)response; - - EnsureSuccessStatusCode(client, httpResponse, options); + await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); options.CancellationToken.ThrowIfCancellationRequested(); - var contentLength = GetContentLength(httpResponse); - - using (var stream = httpResponse.GetResponseStream()) + using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true)) { await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); @@ -547,35 +501,22 @@ namespace Emby.Server.Implementations.HttpClientManager options.Progress.Report(100); - return GetResponseInfo(httpResponse, tempFile, contentLength); + var contentLength = response.Content.Headers.ContentLength; + return GetResponseInfo(response, tempFile, contentLength); } } catch (Exception ex) { - DeleteTempFile(tempFile); - throw GetException(ex, options, client); - } - finally - { - options.ResourcePool?.Release(); - } - } - - private static long? GetContentLength(HttpWebResponse response) - { - var length = response.ContentLength; + if (File.Exists(tempFile)) + { + File.Delete(tempFile); + } - if (length == 0) - { - return null; + throw GetException(ex, options); } - - return length; } - protected static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - private Exception GetException(Exception ex, HttpRequestOptions options, HttpClientInfo client) + private Exception GetException(Exception ex, HttpRequestOptions options) { if (ex is HttpException) { @@ -599,11 +540,6 @@ namespace Emby.Server.Implementations.HttpClientManager if (response != null) { exception.StatusCode = response.StatusCode; - - if ((int)response.StatusCode == 429) - { - client.LastTimeout = DateTime.UtcNow; - } } } @@ -624,7 +560,7 @@ namespace Emby.Server.Implementations.HttpClientManager if (operationCanceledException != null) { - return GetCancellationException(options, client, options.CancellationToken, operationCanceledException); + return GetCancellationException(options, options.CancellationToken, operationCanceledException); } if (options.LogErrors) @@ -635,18 +571,6 @@ namespace Emby.Server.Implementations.HttpClientManager return ex; } - private void DeleteTempFile(string file) - { - try - { - _fileSystem.DeleteFile(file); - } - catch (IOException) - { - // Might not have been created at all. No need to worry. - } - } - private void ValidateParams(HttpRequestOptions options) { if (string.IsNullOrEmpty(options.Url)) @@ -682,11 +606,10 @@ namespace Emby.Server.Implementations.HttpClientManager /// Throws the cancellation exception. /// </summary> /// <param name="options">The options.</param> - /// <param name="client">The client.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="exception">The exception.</param> /// <returns>Exception.</returns> - private Exception GetCancellationException(HttpRequestOptions options, HttpClientInfo client, CancellationToken cancellationToken, OperationCanceledException exception) + private Exception GetCancellationException(HttpRequestOptions options, CancellationToken cancellationToken, OperationCanceledException exception) { // If the HttpClient's timeout is reached, it will cancel the Task internally if (!cancellationToken.IsCancellationRequested) @@ -698,8 +621,6 @@ namespace Emby.Server.Implementations.HttpClientManager _logger.LogError(msg); } - client.LastTimeout = DateTime.UtcNow; - // Throw an HttpException so that the caller doesn't think it was cancelled by user code return new HttpException(msg, exception) { @@ -710,91 +631,20 @@ namespace Emby.Server.Implementations.HttpClientManager return exception; } - private void EnsureSuccessStatusCode(HttpClientInfo client, HttpWebResponse response, HttpRequestOptions options) + private async Task EnsureSuccessStatusCode(HttpResponseMessage response, HttpRequestOptions options) { - var statusCode = response.StatusCode; - - var isSuccessful = statusCode >= HttpStatusCode.OK && statusCode <= (HttpStatusCode)299; - - if (isSuccessful) + if (response.IsSuccessStatusCode) { return; } - if (options.LogErrorResponseBody) - { - try - { - using (var stream = response.GetResponseStream()) - { - if (stream != null) - { - using (var reader = new StreamReader(stream)) - { - var msg = reader.ReadToEnd(); - - _logger.LogError(msg); - } - } - } - } - catch - { - - } - } + var msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + _logger.LogError(msg); - throw new HttpException(response.StatusDescription) + throw new HttpException(response.ReasonPhrase) { StatusCode = response.StatusCode }; } - - private static Task<WebResponse> GetResponseAsync(WebRequest request, TimeSpan timeout) - { - var taskCompletion = new TaskCompletionSource<WebResponse>(); - - var asyncTask = Task.Factory.FromAsync(request.BeginGetResponse, request.EndGetResponse, null); - - ThreadPool.RegisterWaitForSingleObject((asyncTask as IAsyncResult).AsyncWaitHandle, TimeoutCallback, request, timeout, true); - var callback = new TaskCallback { taskCompletion = taskCompletion }; - asyncTask.ContinueWith(callback.OnSuccess, TaskContinuationOptions.NotOnFaulted); - - // Handle errors - asyncTask.ContinueWith(callback.OnError, TaskContinuationOptions.OnlyOnFaulted); - - return taskCompletion.Task; - } - - private static void TimeoutCallback(object state, bool timedOut) - { - if (timedOut && state != null) - { - var request = (WebRequest)state; - request.Abort(); - } - } - - private class TaskCallback - { - public TaskCompletionSource<WebResponse> taskCompletion; - - public void OnSuccess(Task<WebResponse> task) - { - taskCompletion.TrySetResult(task.Result); - } - - public void OnError(Task<WebResponse> task) - { - if (task.Exception == null) - { - taskCompletion.TrySetException(Enumerable.Empty<Exception>()); - } - else - { - taskCompletion.TrySetException(task.Exception); - } - } - } } } diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 0527464ff..fe09b07ff 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -165,6 +165,34 @@ namespace Emby.Server.Implementations.Library return user.Password; } + public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) + { + ConvertPasswordFormat(user); + + if (newPassword != null) + { + newPasswordHash = string.Format("$SHA1${0}", GetHashedString(user, newPassword)); + } + + if (string.IsNullOrWhiteSpace(newPasswordHash)) + { + throw new ArgumentNullException(nameof(newPasswordHash)); + } + + user.EasyPassword = newPasswordHash; + } + + public string GetEasyPasswordHash(User user) + { + // This should be removed in the future. This was added to let user login after + // Jellyfin 10.3.3 failed to save a well formatted PIN. + ConvertPasswordFormat(user); + + return string.IsNullOrEmpty(user.EasyPassword) + ? null + : (new PasswordHash(user.EasyPassword)).Hash; + } + public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) { passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); diff --git a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs new file mode 100644 index 000000000..25d233137 --- /dev/null +++ b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Net; + +namespace Emby.Server.Implementations.Library +{ + public class InvalidAuthProvider : IAuthenticationProvider + { + public string Name => "InvalidOrMissingAuthenticationProvider"; + + public bool IsEnabled => true; + + public Task<ProviderAuthenticationResult> Authenticate(string username, string password) + { + throw new SecurityException("User Account cannot login with this provider. The Normal provider for this user cannot be found"); + } + + public Task<bool> HasPassword(User user) + { + return Task.FromResult(true); + } + + public Task ChangePassword(User user, string newPassword) + { + return Task.CompletedTask; + } + + public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) + { + // Nothing here + } + + public string GetPasswordHash(User user) + { + return string.Empty; + } + + public string GetEasyPasswordHash(User user) + { + return string.Empty; + } + } +} diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index b396ee51a..ff375e590 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -79,6 +79,8 @@ namespace Emby.Server.Implementations.Library private IAuthenticationProvider[] _authenticationProviders; private DefaultAuthenticationProvider _defaultAuthenticationProvider; + private InvalidAuthProvider _invalidAuthProvider; + private IPasswordResetProvider[] _passwordResetProviders; private DefaultPasswordResetProvider _defaultPasswordResetProvider; @@ -141,6 +143,8 @@ namespace Emby.Server.Implementations.Library _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First(); + _invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First(); + _passwordResetProviders = passwordResetProviders.ToArray(); _defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First(); @@ -307,8 +311,7 @@ namespace Emby.Server.Implementations.Library user = Users .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); - var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy; - if (hasNewUserPolicy != null) + if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy) { var policy = hasNewUserPolicy.GetNewUserPolicy(); UpdateUserPolicy(user, policy, true); @@ -400,7 +403,9 @@ namespace Emby.Server.Implementations.Library if (providers.Length == 0) { - providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider }; + // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found + _logger.LogWarning("User {UserName} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected", user.Name, user.Policy.AuthenticationProviderId); + providers = new IAuthenticationProvider[] { _invalidAuthProvider }; } return providers; @@ -471,7 +476,7 @@ namespace Emby.Server.Implementations.Library if (password == null) { // legacy - success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + success = string.Equals(GetAuthenticationProvider(user).GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); } else { @@ -497,11 +502,11 @@ namespace Emby.Server.Implementations.Library if (password == null) { // legacy - success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); } else { - success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); + success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); } } } @@ -546,13 +551,6 @@ namespace Emby.Server.Implementations.Library } } - private string GetLocalPasswordHash(User user) - { - return string.IsNullOrEmpty(user.EasyPassword) - ? null - : (new PasswordHash(user.EasyPassword)).Hash; - } - /// <summary> /// Loads the users from the repository /// </summary> @@ -596,7 +594,7 @@ namespace Emby.Server.Implementations.Library } bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; - bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetLocalPasswordHash(user)); + bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user)); bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : @@ -884,17 +882,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(user)); } - if (newPassword != null) - { - newPasswordHash = _defaultAuthenticationProvider.GetHashedString(user, newPassword); - } - - if (string.IsNullOrWhiteSpace(newPasswordHash)) - { - throw new ArgumentNullException(nameof(newPasswordHash)); - } - - user.EasyPassword = newPasswordHash; + GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordHash); UpdateUser(user); diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 4137760d0..f3f747718 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -96,8 +96,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings Url = ApiUrl + "/schedules", UserAgent = UserAgent, CancellationToken = cancellationToken, - // The data can be large so give it some extra time - TimeoutMs = 60000, LogErrorResponseBody = true, RequestContent = requestString }; @@ -115,9 +113,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings Url = ApiUrl + "/programs", UserAgent = UserAgent, CancellationToken = cancellationToken, - LogErrorResponseBody = true, - // The data can be large so give it some extra time - TimeoutMs = 60000 + LogErrorResponseBody = true }; httpOptions.RequestHeaders["token"] = token; @@ -483,8 +479,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings CancellationToken = cancellationToken, RequestContent = imageIdString, LogErrorResponseBody = true, - // The data can be large so give it some extra time - TimeoutMs = 60000 }; try @@ -871,8 +865,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings UserAgent = UserAgent, CancellationToken = cancellationToken, LogErrorResponseBody = true, - // The data can be large so give it some extra time - TimeoutMs = 60000 }; httpOptions.RequestHeaders["token"] = token; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 24b100edd..761275f8f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -138,7 +138,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { Url = string.Format("{0}/discover.json", GetApiUrl(info)), CancellationToken = cancellationToken, - TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(10).TotalMilliseconds), BufferContent = false }, "GET").ConfigureAwait(false)) @@ -191,7 +190,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { Url = string.Format("{0}/tuners.html", GetApiUrl(info)), CancellationToken = cancellationToken, - TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds), BufferContent = false })) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index d74cf3be2..e8b34da0c 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -47,13 +47,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts CancellationToken = CancellationToken.None, BufferContent = false, - // Increase a little bit - TimeoutMs = 30000, - EnableHttpCompression = false, - - LogResponse = true, - LogResponseHeaders = true }; foreach (var header in mediaSource.RequiredHttpHeaders) diff --git a/Emby.Server.Implementations/ResourceFileManager.cs b/Emby.Server.Implementations/ResourceFileManager.cs index 890d848f4..6eda2b503 100644 --- a/Emby.Server.Implementations/ResourceFileManager.cs +++ b/Emby.Server.Implementations/ResourceFileManager.cs @@ -1,10 +1,8 @@ using System; using System.IO; -using System.Threading.Tasks; using MediaBrowser.Controller; using MediaBrowser.Controller.Net; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations @@ -13,34 +11,14 @@ namespace Emby.Server.Implementations { private readonly IFileSystem _fileSystem; private readonly ILogger _logger; - private readonly IHttpResultFactory _resultFactory; - public ResourceFileManager( - IHttpResultFactory resultFactory, - ILoggerFactory loggerFactory, - IFileSystem fileSystem) + public ResourceFileManager(ILogger<ResourceFileManager> logger, IFileSystem fileSystem) { - _resultFactory = resultFactory; - _logger = loggerFactory.CreateLogger("ResourceManager"); + _logger = logger; _fileSystem = fileSystem; } - public Stream GetResourceFileStream(string basePath, string virtualPath) - { - return _fileSystem.GetFileStream(GetResourcePath(basePath, virtualPath), FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, true); - } - - public Task<object> GetStaticFileResult(IRequest request, string basePath, string virtualPath, string contentType, TimeSpan? cacheDuration) - { - return _resultFactory.GetStaticFileResult(request, GetResourcePath(basePath, virtualPath)); - } - - public string ReadAllText(string basePath, string virtualPath) - { - return File.ReadAllText(GetResourcePath(basePath, virtualPath)); - } - - private string GetResourcePath(string basePath, string virtualPath) + public string GetResourcePath(string basePath, string virtualPath) { var fullPath = Path.Combine(basePath, virtualPath.Replace('/', Path.DirectorySeparatorChar)); @@ -50,7 +28,7 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - _logger.LogError(ex, "Error in Path.GetFullPath"); + _logger.LogError(ex, "Error retrieving full path"); } // Don't allow file system access outside of the source folder diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index c81a93767..29b8dfd3d 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -15,13 +15,9 @@ namespace Emby.Server.Implementations.Security { public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository { - private readonly IServerConfigurationManager _config; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public AuthenticationRepository(ILoggerFactory loggerFactory, IServerConfigurationManager config) : base(loggerFactory.CreateLogger(nameof(AuthenticationRepository))) { - _config = config; DbFilePath = Path.Combine(config.ApplicationPaths.DataPath, "authentication.db"); } diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index 6a522fbef..c27eb7686 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.Services foreach (var propertyInfo in RestPath.GetSerializableProperties(type)) { - var propertySetFn = TypeAccessor.GetSetPropertyMethod(type, propertyInfo); + var propertySetFn = TypeAccessor.GetSetPropertyMethod(propertyInfo); var propertyType = propertyInfo.PropertyType; var propertyParseStringFn = GetParseFn(propertyType); var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn, propertyType); @@ -110,9 +110,9 @@ namespace Emby.Server.Implementations.Services } } - internal class TypeAccessor + internal static class TypeAccessor { - public static Action<object, object> GetSetPropertyMethod(Type type, PropertyInfo propertyInfo) + public static Action<object, object> GetSetPropertyMethod(PropertyInfo propertyInfo) { if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 985748caf..53ed5fc22 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -7,7 +7,6 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Devices; @@ -25,7 +24,6 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; @@ -53,8 +51,6 @@ namespace Emby.Server.Implementations.Session private readonly IImageProcessor _imageProcessor; private readonly IMediaSourceManager _mediaSourceManager; - private readonly IHttpClient _httpClient; - private readonly IJsonSerializer _jsonSerializer; private readonly IServerApplicationHost _appHost; private readonly IAuthenticationRepository _authRepo; @@ -96,9 +92,7 @@ namespace Emby.Server.Implementations.Session IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, - IJsonSerializer jsonSerializer, IServerApplicationHost appHost, - IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager) @@ -110,9 +104,7 @@ namespace Emby.Server.Implementations.Session _musicManager = musicManager; _dtoService = dtoService; _imageProcessor = imageProcessor; - _jsonSerializer = jsonSerializer; _appHost = appHost; - _httpClient = httpClient; _authRepo = authRepo; _deviceManager = deviceManager; _mediaSourceManager = mediaSourceManager; @@ -347,8 +339,7 @@ namespace Emby.Server.Implementations.Session var runtimeTicks = libraryItem.RunTimeTicks; MediaSourceInfo mediaSource = null; - var hasMediaSources = libraryItem as IHasMediaSources; - if (hasMediaSources != null) + if (libraryItem is IHasMediaSources hasMediaSources) { mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); @@ -1046,6 +1037,24 @@ namespace Emby.Server.Implementations.Session } } + private static Task SendMessageToSessions<T>(IEnumerable<SessionInfo> sessions, string name, T data, CancellationToken cancellationToken) + { + IEnumerable<Task> GetTasks() + { + var messageId = Guid.NewGuid().ToString("N"); + foreach (var session in sessions) + { + var controllers = session.SessionControllers; + foreach (var controller in controllers) + { + yield return controller.SendMessage(name, messageId, data, controllers, cancellationToken); + } + } + } + + return Task.WhenAll(GetTasks()); + } + public async Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken) { CheckDisposed(); @@ -1232,12 +1241,13 @@ namespace Emby.Server.Implementations.Session return SendMessageToSession(session, "Playstate", command, cancellationToken); } - private void AssertCanControl(SessionInfo session, SessionInfo controllingSession) + private static void AssertCanControl(SessionInfo session, SessionInfo controllingSession) { if (session == null) { throw new ArgumentNullException(nameof(session)); } + if (controllingSession == null) { throw new ArgumentNullException(nameof(controllingSession)); @@ -1249,26 +1259,11 @@ namespace Emby.Server.Implementations.Session /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - public async Task SendRestartRequiredNotification(CancellationToken cancellationToken) + public Task SendRestartRequiredNotification(CancellationToken cancellationToken) { CheckDisposed(); - var sessions = Sessions.ToList(); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await SendMessageToSession(session, "RestartRequired", string.Empty, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError("Error in SendRestartRequiredNotification.", ex); - } - - }, cancellationToken)).ToArray(); - - await Task.WhenAll(tasks).ConfigureAwait(false); + return SendMessageToSessions(Sessions, "RestartRequired", string.Empty, cancellationToken); } /// <summary> @@ -1280,22 +1275,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); - var sessions = Sessions.ToList(); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await SendMessageToSession(session, "ServerShuttingDown", string.Empty, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError("Error in SendServerShutdownNotification.", ex); - } - - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + return SendMessageToSessions(Sessions, "ServerShuttingDown", string.Empty, cancellationToken); } /// <summary> @@ -1309,22 +1289,7 @@ namespace Emby.Server.Implementations.Session _logger.LogDebug("Beginning SendServerRestartNotification"); - var sessions = Sessions.ToList(); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await SendMessageToSession(session, "ServerRestarting", string.Empty, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError("Error in SendServerRestartNotification.", ex); - } - - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + return SendMessageToSessions(Sessions, "ServerRestarting", string.Empty, cancellationToken); } /// <summary> @@ -1841,64 +1806,23 @@ namespace Emby.Server.Implementations.Session var data = dataFn(); - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError("Error sending message", ex); - } - - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + return SendMessageToSessions(sessions, name, data, cancellationToken); } public Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, T data, CancellationToken cancellationToken) { CheckDisposed(); - var sessions = Sessions.Where(i => userIds.Any(i.ContainsUser)).ToList(); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError("Error sending message", ex); - } - - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + var sessions = Sessions.Where(i => userIds.Any(i.ContainsUser)); + return SendMessageToSessions(sessions, name, data, cancellationToken); } public Task SendMessageToUserDeviceSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken) { CheckDisposed(); - var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)).ToList(); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError("Error sending message", ex); - } - - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); + return SendMessageToSessions(sessions, name, data, cancellationToken); } public Task SendMessageToUserDeviceAndAdminSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken) @@ -1906,23 +1830,8 @@ namespace Emby.Server.Implementations.Session CheckDisposed(); var sessions = Sessions - .Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i)) - .ToList(); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError("Error sending message", ex); - } - - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + .Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i)); + return SendMessageToSessions(sessions, name, data, cancellationToken); } private bool IsAdminSession(SessionInfo s) diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 8e6ed7a7e..b9b0cc382 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -1,7 +1,8 @@ using System.Collections.Generic; using System.Reflection; using Emby.Server.Implementations; -using Emby.Server.Implementations.HttpServer; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.IO; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -15,8 +16,8 @@ namespace Jellyfin.Server ILoggerFactory loggerFactory, StartupOptions options, IFileSystem fileSystem, - MediaBrowser.Controller.Drawing.IImageEncoder imageEncoder, - MediaBrowser.Common.Net.INetworkManager networkManager, + IImageEncoder imageEncoder, + INetworkManager networkManager, IConfiguration configuration) : base( applicationPaths, diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 81f145abf..641b3f182 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -26,7 +26,7 @@ <!-- Code analysers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.2" /> + <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.3" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" /> </ItemGroup> @@ -36,17 +36,17 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="CommandLineParser" Version="2.4.3" /> - <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.2.0" /> + <PackageReference Include="CommandLineParser" Version="2.5.0" /> + <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.2.4" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" /> <PackageReference Include="Serilog.AspNetCore" Version="2.1.1" /> - <PackageReference Include="Serilog.Settings.Configuration" Version="3.0.1" /> - <PackageReference Include="Serilog.Sinks.Async" Version="1.3.0" /> + <PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" /> + <PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" /> <PackageReference Include="Serilog.Sinks.File" Version="4.0.0" /> <PackageReference Include="SkiaSharp" Version="1.68.0" /> - <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.13" /> - <PackageReference Include="SQLitePCLRaw.provider.sqlite3.netstandard11" Version="1.1.13" /> + <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.14" /> + <PackageReference Include="SQLitePCLRaw.provider.sqlite3.netstandard11" Version="1.1.14" /> </ItemGroup> <ItemGroup> diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 3c7ad1d0a..f1ae48492 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -224,7 +224,7 @@ namespace MediaBrowser.Api.UserLibrary request.IncludeItemTypes = "Playlist"; } - if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id)) + if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id)) { Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Name, item.Name); return new QueryResult<BaseItem> diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index bea178517..38e0ff0f5 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -28,6 +28,7 @@ namespace MediaBrowser.Common.Net get => GetHeaderValue(HeaderNames.Accept); set => RequestHeaders[HeaderNames.Accept] = value; } + /// <summary> /// Gets or sets the cancellation token. /// </summary> @@ -35,12 +36,6 @@ namespace MediaBrowser.Common.Net public CancellationToken CancellationToken { get; set; } /// <summary> - /// Gets or sets the resource pool. - /// </summary> - /// <value>The resource pool.</value> - public SemaphoreSlim ResourcePool { get; set; } - - /// <summary> /// Gets or sets the user agent. /// </summary> /// <value>The user agent.</value> @@ -86,8 +81,6 @@ namespace MediaBrowser.Common.Net public bool LogRequest { get; set; } public bool LogRequestAsDebug { get; set; } public bool LogErrors { get; set; } - public bool LogResponse { get; set; } - public bool LogResponseHeaders { get; set; } public bool LogErrorResponseBody { get; set; } public bool EnableKeepAlive { get; set; } @@ -95,11 +88,9 @@ namespace MediaBrowser.Common.Net public CacheMode CacheMode { get; set; } public TimeSpan CacheLength { get; set; } - public int TimeoutMs { get; set; } public bool EnableDefaultUserAgent { get; set; } public bool AppendCharsetToMimeType { get; set; } - public string DownloadFilePath { get; set; } private string GetHeaderValue(string name) { @@ -120,17 +111,6 @@ namespace MediaBrowser.Common.Net LogRequest = true; LogErrors = true; CacheMode = CacheMode.None; - - TimeoutMs = 20000; - } - - public void SetPostData(IDictionary<string, string> values) - { - var strings = values.Keys.Select(key => string.Format("{0}={1}", key, values[key])); - var postContent = string.Join("&", strings.ToArray()); - - RequestContent = postContent; - RequestContentType = "application/x-www-form-urlencoded"; } } diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs index 5aaf7e0be..db69c6f2c 100644 --- a/MediaBrowser.Common/Net/IHttpClient.cs +++ b/MediaBrowser.Common/Net/IHttpClient.cs @@ -1,5 +1,6 @@ using System.IO; using System.Threading.Tasks; +using System.Net.Http; namespace MediaBrowser.Common.Net { @@ -23,6 +24,8 @@ namespace MediaBrowser.Common.Net Task<Stream> Get(HttpRequestOptions options); /// <summary> + /// Warning: Deprecated function, + /// use 'Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, HttpMethod httpMethod);' instead /// Sends the asynchronous. /// </summary> /// <param name="options">The options.</param> @@ -31,6 +34,14 @@ namespace MediaBrowser.Common.Net Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod); /// <summary> + /// Sends the asynchronous. + /// </summary> + /// <param name="options">The options.</param> + /// <param name="httpMethod">The HTTP method.</param> + /// <returns>Task{HttpResponseInfo}.</returns> + Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, HttpMethod httpMethod); + + /// <summary> /// Posts the specified options. /// </summary> /// <param name="options">The options.</param> diff --git a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs index b9f282bd2..2cf531eed 100644 --- a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs +++ b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs @@ -11,6 +11,9 @@ namespace MediaBrowser.Controller.Authentication Task<ProviderAuthenticationResult> Authenticate(string username, string password); Task<bool> HasPassword(User user); Task ChangePassword(User user, string newPassword); + void ChangeEasyPassword(User user, string newPassword, string newPasswordHash); + string GetPasswordHash(User user); + string GetEasyPasswordHash(User user); } public interface IRequiresResolvedUser diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index 0d5f508dd..9952ba418 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -228,7 +228,15 @@ namespace MediaBrowser.Controller.Entities return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, safeFolderName); } - return System.IO.Path.Combine(parentPath, Id.ToString("N")); + // TODO: Remove idPath and just use usernamePath for future releases + var usernamePath = System.IO.Path.Combine(parentPath, username); + var idPath = System.IO.Path.Combine(parentPath, Id.ToString("N")); + if (!Directory.Exists(usernamePath) && Directory.Exists(idPath)) + { + Directory.Move(idPath, usernamePath); + } + + return usernamePath; } public bool IsParentalScheduleAllowed() diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 3e2191376..4a6d32dce 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; @@ -17,7 +16,6 @@ namespace MediaBrowser.Controller.Entities public Guid? UserId { get; set; } public static ITVSeriesManager TVSeriesManager; - public static IPlaylistManager PlaylistManager; [IgnoreDataMember] public string CollectionType => ViewType; @@ -38,6 +36,7 @@ namespace MediaBrowser.Controller.Entities { list.Add(Id); } + return list; } @@ -65,7 +64,7 @@ namespace MediaBrowser.Controller.Entities parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent; } - return new UserViewBuilder(UserViewManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, ConfigurationManager, PlaylistManager) + return new UserViewBuilder(UserViewManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, ConfigurationManager) .GetUserItems(parent, this, CollectionType, query); } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 683218a9e..e483c8f34 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -5,7 +5,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; @@ -21,9 +20,14 @@ namespace MediaBrowser.Controller.Entities private readonly IUserDataManager _userDataManager; private readonly ITVSeriesManager _tvSeriesManager; private readonly IServerConfigurationManager _config; - private readonly IPlaylistManager _playlistManager; - public UserViewBuilder(IUserViewManager userViewManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, IServerConfigurationManager config, IPlaylistManager playlistManager) + public UserViewBuilder( + IUserViewManager userViewManager, + ILibraryManager libraryManager, + ILogger logger, + IUserDataManager userDataManager, + ITVSeriesManager tvSeriesManager, + IServerConfigurationManager config) { _userViewManager = userViewManager; _libraryManager = libraryManager; @@ -31,7 +35,6 @@ namespace MediaBrowser.Controller.Entities _userDataManager = userDataManager; _tvSeriesManager = tvSeriesManager; _config = config; - _playlistManager = playlistManager; } public QueryResult<BaseItem> GetUserItems(Folder queryParent, Folder displayParent, string viewType, InternalItemsQuery query) @@ -110,6 +113,7 @@ namespace MediaBrowser.Controller.Entities { return GetResult(GetMediaFolders(user).OfType<Folder>().SelectMany(i => i.GetChildren(user, true)), queryParent, query); } + return queryParent.GetItems(query); } } diff --git a/MediaBrowser.Controller/IResourceFileManager.cs b/MediaBrowser.Controller/IResourceFileManager.cs index f70ea6a17..69a51cec8 100644 --- a/MediaBrowser.Controller/IResourceFileManager.cs +++ b/MediaBrowser.Controller/IResourceFileManager.cs @@ -1,16 +1,7 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; - namespace MediaBrowser.Controller { public interface IResourceFileManager { - Task<object> GetStaticFileResult(IRequest request, string basePath, string virtualPath, string contentType, TimeSpan? cacheDuration); - - Stream GetResourceFileStream(string basePath, string virtualPath); - - string ReadAllText(string basePath, string virtualPath); + string GetResourcePath(string basePath, string virtualPath); } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index e378c2b89..2984efec3 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1083,27 +1083,51 @@ namespace MediaBrowser.Controller.MediaEncoding { var bitrate = request.VideoBitRate; - // If specific values were requested, then force the caller to supply a bitrate as well - if (request.Height.HasValue && request.Width.HasValue) - { - return bitrate; - } - if (videoStream != null) { - if (bitrate.HasValue) - { - var inputVideoCodec = videoStream.Codec; - bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec); + var isUpscaling = request.Height.HasValue && videoStream.Height.HasValue && + request.Height.Value > videoStream.Height.Value && request.Width.HasValue && videoStream.Width.HasValue && + request.Width.Value > videoStream.Width.Value; - // If a max bitrate was requested, don't let the scaled bitrate exceed it - if (request.VideoBitRate.HasValue) + // Don't allow bitrate increases unless upscaling + if (!isUpscaling) + { + if (bitrate.HasValue && videoStream.BitRate.HasValue) { - bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value); + bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value); } } } + if (bitrate.HasValue) + { + var inputVideoCodec = videoStream.Codec; + bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec); + + // If a max bitrate was requested, don't let the scaled bitrate exceed it + if (request.VideoBitRate.HasValue) + { + bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value); + } + } + + return bitrate; + } + + private int GetMinBitrate(int sourceBitrate, int requestedBitrate) + { + // these values were chosen from testing to improve low bitrate streams + if (sourceBitrate <= 2000000) + { + sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5); + } + else if (sourceBitrate <= 3000000) + { + sourceBitrate = Convert.ToInt32(sourceBitrate * 2); + } + + var bitrate = Math.Min(sourceBitrate, requestedBitrate); + return bitrate; } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 3eed891cb..b00350875 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -207,6 +207,7 @@ namespace MediaBrowser.MediaEncoding.Encoder "hevc_omx", "h264_vaapi", "hevc_vaapi", + "h264_v4l2m2m", "ac3" }; diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index e4757543e..c0f92ac4a 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -18,7 +18,7 @@ <ItemGroup> <PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.1" /> - <PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" /> + <PackageReference Include="UTF.Unknown" Version="1.0.0" /> </ItemGroup> </Project> diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index d978359c7..8677b363f 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -126,8 +126,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw new ArgumentNullException(nameof(mediaSourceId)); } - // TODO network path substition useful ? - var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(item, null, true, true, cancellationToken).ConfigureAwait(false); + var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(item, null, true, false, cancellationToken).ConfigureAwait(false); var mediaSource = mediaSources .First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index cfbb85ea6..5941ed436 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -14,7 +14,7 @@ <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.2.0" /> <PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" /> - <PackageReference Include="PlaylistsNET" Version="1.0.2" /> + <PackageReference Include="PlaylistsNET" Version="1.0.4" /> <PackageReference Include="TvDbSharper" Version="2.0.0" /> </ItemGroup> diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs b/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs index efb8a0fe8..1d1fbd00f 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs @@ -33,7 +33,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB get { // Refresh if necessary - if (_tokenCreatedAt > DateTime.Now.Subtract(TimeSpan.FromHours(20))) + if (_tokenCreatedAt < DateTime.Now.Subtract(TimeSpan.FromHours(20))) { try { diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 58ab2d27b..d2ffd5efc 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -114,8 +114,6 @@ namespace MediaBrowser.WebDashboard.Api private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IFileSystem _fileSystem; - private readonly ILocalizationManager _localization; - private readonly IJsonSerializer _jsonSerializer; private IResourceFileManager _resourceFileManager; /// <summary> @@ -126,16 +124,12 @@ namespace MediaBrowser.WebDashboard.Api IResourceFileManager resourceFileManager, IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem, - ILocalizationManager localization, - IJsonSerializer jsonSerializer, ILogger logger, IHttpResultFactory resultFactory) { _appHost = appHost; _serverConfigurationManager = serverConfigurationManager; _fileSystem = fileSystem; - _localization = localization; - _jsonSerializer = jsonSerializer; _logger = logger; _resultFactory = resultFactory; _resourceFileManager = resourceFileManager; @@ -205,6 +199,7 @@ namespace MediaBrowser.WebDashboard.Api { return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.js"), () => Task.FromResult(stream)); } + if (isTemplate) { return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => Task.FromResult(stream)); @@ -316,7 +311,7 @@ namespace MediaBrowser.WebDashboard.Api // Bounce them to the startup wizard if it hasn't been completed yet if (!_serverConfigurationManager.Configuration.IsStartupWizardCompleted && Request.RawUrl.IndexOf("wizard", StringComparison.OrdinalIgnoreCase) == -1 && - GetPackageCreator(basePath).IsCoreHtml(path)) + PackageCreator.IsCoreHtml(path)) { // But don't redirect if an html import is being requested. if (path.IndexOf("bower_components", StringComparison.OrdinalIgnoreCase) == -1) @@ -355,7 +350,7 @@ namespace MediaBrowser.WebDashboard.Api return await _resultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(basePath, path, localizationCulture)).ConfigureAwait(false); } - return await _resourceFileManager.GetStaticFileResult(Request, basePath, path, contentType, cacheDuration); + return await _resultFactory.GetStaticFileResult(Request, _resourceFileManager.GetResourcePath(basePath, path)); } private string GetLocalizationCulture() @@ -374,7 +369,7 @@ namespace MediaBrowser.WebDashboard.Api private PackageCreator GetPackageCreator(string basePath) { - return new PackageCreator(basePath, _fileSystem, _logger, _serverConfigurationManager, _resourceFileManager); + return new PackageCreator(basePath, _resourceFileManager); } public async Task<object> Get(GetDashboardPackage request) diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index 2d0e0e188..133bf61e8 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -1,139 +1,108 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; using System.Threading.Tasks; using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Model.IO; -using Microsoft.Extensions.Logging; namespace MediaBrowser.WebDashboard.Api { public class PackageCreator { - private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; - private readonly IServerConfigurationManager _config; private readonly string _basePath; - private IResourceFileManager _resourceFileManager; + private readonly IResourceFileManager _resourceFileManager; - public PackageCreator(string basePath, IFileSystem fileSystem, ILogger logger, IServerConfigurationManager config, IResourceFileManager resourceFileManager) + public PackageCreator(string basePath, IResourceFileManager resourceFileManager) { - _fileSystem = fileSystem; - _logger = logger; - _config = config; _basePath = basePath; _resourceFileManager = resourceFileManager; } - public async Task<Stream> GetResource(string virtualPath, + public async Task<Stream> GetResource( + string virtualPath, string mode, string localizationCulture, string appVersion) { - var resourceStream = GetRawResourceStream(virtualPath); + var resourcePath = _resourceFileManager.GetResourcePath(_basePath, virtualPath); + Stream resourceStream = File.OpenRead(resourcePath); - if (resourceStream != null) + if (resourceStream != null && IsCoreHtml(virtualPath)) { - if (IsFormat(virtualPath, "html")) - { - if (IsCoreHtml(virtualPath)) - { - resourceStream = await ModifyHtml(virtualPath, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false); - } - } + resourceStream = await ModifyHtml(virtualPath, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false); } return resourceStream; } - /// <summary> - /// Determines whether the specified path is HTML. - /// </summary> - /// <param name="path">The path.</param> - /// <param name="format">The format.</param> - /// <returns><c>true</c> if the specified path is HTML; otherwise, <c>false</c>.</returns> - private static bool IsFormat(string path, string format) - { - return Path.GetExtension(path).EndsWith(format, StringComparison.OrdinalIgnoreCase); - } - - public bool IsCoreHtml(string path) + public static bool IsCoreHtml(string path) { if (path.IndexOf(".template.html", StringComparison.OrdinalIgnoreCase) != -1) { return false; } - return IsFormat(path, "html"); + return string.Equals(Path.GetExtension(path), ".html", StringComparison.OrdinalIgnoreCase); } /// <summary> /// Modifies the HTML by adding common meta tags, css and js. /// </summary> /// <returns>Task{Stream}.</returns> - public async Task<Stream> ModifyHtml(string path, Stream sourceStream, string mode, string appVersion, string localizationCulture) + public async Task<Stream> ModifyHtml( + string path, + Stream sourceStream, + string mode, + string appVersion, + string localizationCulture) { var isMainIndexPage = string.Equals(path, "index.html", StringComparison.OrdinalIgnoreCase); - using (sourceStream) + string html; + using (var reader = new StreamReader(sourceStream, Encoding.UTF8)) { - string html; - - using (var memoryStream = new MemoryStream()) - { - await sourceStream.CopyToAsync(memoryStream).ConfigureAwait(false); - - var originalBytes = memoryStream.ToArray(); + html = await reader.ReadToEndAsync().ConfigureAwait(false); + } - html = Encoding.UTF8.GetString(originalBytes, 0, originalBytes.Length); + if (isMainIndexPage && !string.IsNullOrWhiteSpace(localizationCulture)) + { + var lang = localizationCulture.Split('-')[0]; - if (isMainIndexPage) - { - if (!string.IsNullOrWhiteSpace(localizationCulture)) - { - var lang = localizationCulture.Split('-').FirstOrDefault(); + html = html.Replace("<html", "<html data-culture=\"" + localizationCulture + "\" lang=\"" + lang + "\""); + } - html = html.Replace("<html", "<html data-culture=\"" + localizationCulture + "\" lang=\"" + lang + "\""); - } - } - } + if (isMainIndexPage) + { + html = html.Replace("<head>", "<head>" + GetMetaTags(mode)); + } - if (isMainIndexPage) - { - html = html.Replace("<head>", "<head>" + GetMetaTags(mode)); - } + // Disable embedded scripts from plugins. We'll run them later once resources have loaded + if (html.IndexOf("<script", StringComparison.OrdinalIgnoreCase) != -1) + { + html = html.Replace("<script", "<!--<script"); + html = html.Replace("</script>", "</script>-->"); + } - // Disable embedded scripts from plugins. We'll run them later once resources have loaded - if (html.IndexOf("<script", StringComparison.OrdinalIgnoreCase) != -1) - { - html = html.Replace("<script", "<!--<script"); - html = html.Replace("</script>", "</script>-->"); - } + if (isMainIndexPage) + { + html = html.Replace("</body>", GetCommonJavascript(mode, appVersion) + "</body>"); + } - if (isMainIndexPage) - { - html = html.Replace("</body>", GetCommonJavascript(mode, appVersion) + "</body>"); - } + var bytes = Encoding.UTF8.GetBytes(html); - var bytes = Encoding.UTF8.GetBytes(html); + return new MemoryStream(bytes); - return new MemoryStream(bytes); - } } /// <summary> /// Gets the meta tags. /// </summary> /// <returns>System.String.</returns> - private string GetMetaTags(string mode) + private static string GetMetaTags(string mode) { var sb = new StringBuilder(); - if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) || - string.Equals(mode, "android", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) + || string.Equals(mode, "android", StringComparison.OrdinalIgnoreCase)) { sb.Append("<meta http-equiv=\"Content-Security-Policy\" content=\"default-src * 'self' 'unsafe-inline' 'unsafe-eval' data: gap: file: filesystem: ws: wss:;\">"); } @@ -147,7 +116,7 @@ namespace MediaBrowser.WebDashboard.Api /// <param name="mode">The mode.</param> /// <param name="version">The version.</param> /// <returns>System.String.</returns> - private string GetCommonJavascript(string mode, string version) + private static string GetCommonJavascript(string mode, string version) { var builder = new StringBuilder(); @@ -156,7 +125,6 @@ namespace MediaBrowser.WebDashboard.Api { builder.AppendFormat("window.appMode='{0}';", mode); } - else { builder.AppendFormat("window.dashboardVersion='{0}';", version); @@ -164,31 +132,21 @@ namespace MediaBrowser.WebDashboard.Api builder.Append("</script>"); - var versionString = string.IsNullOrWhiteSpace(mode) ? "?v=" + version : string.Empty; - - var files = new List<string>(); - - files.Add("scripts/apploader.js" + versionString); - if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase)) { - files.Insert(0, "cordova.js"); + builder.Append("<script src=\"cordova.js\" defer></script>"); } - var tags = files.Select(s => string.Format("<script src=\"{0}\" defer></script>", s)).ToArray(); + builder.Append("<script src=\"scripts/apploader.js"); + if (!string.IsNullOrWhiteSpace(version)) + { + builder.Append("?v="); + builder.Append(version); + } - builder.Append(string.Join(string.Empty, tags)); + builder.Append("\" defer></script>"); return builder.ToString(); } - - /// <summary> - /// Gets the raw resource stream. - /// </summary> - private Stream GetRawResourceStream(string virtualPath) - { - return _resourceFileManager.GetResourceFileStream(_basePath, virtualPath); - } - } } diff --git a/MediaBrowser.WebDashboard/jellyfin-web b/MediaBrowser.WebDashboard/jellyfin-web -Subproject b0f7a9b67cc72de98dc357425e9d5c3894c7f37 +Subproject 37636dae5c6c0b0711dfc7612f843b864dd5946 diff --git a/SharedVersion.cs b/SharedVersion.cs index b249520b4..27ba1cf2c 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("10.3.3")] -[assembly: AssemblyFileVersion("10.3.3")] +[assembly: AssemblyVersion("10.3.5")] +[assembly: AssemblyFileVersion("10.3.5")] diff --git a/build.yaml b/build.yaml index ed459582a..cb010ed59 100644 --- a/build.yaml +++ b/build.yaml @@ -1,7 +1,7 @@ --- # We just wrap `build` so this is really it name: "jellyfin" -version: "10.3.3" +version: "10.3.5" packages: - debian-package-x64 - debian-package-armhf diff --git a/deployment/debian-package-x64/pkg-src/changelog b/deployment/debian-package-x64/pkg-src/changelog index a47f7e841..94d0c87df 100644 --- a/deployment/debian-package-x64/pkg-src/changelog +++ b/deployment/debian-package-x64/pkg-src/changelog @@ -1,3 +1,15 @@ +jellyfin (10.3.5-1) unstable; urgency=medium + + * New upstream version 10.3.5; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.5 + + -- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 09 Jun 2019 21:47:35 -0400 + +jellyfin (10.3.4-1) unstable; urgency=medium + + * New upstream version 10.3.4; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.4 + + -- Jellyfin Packaging Team <packaging@jellyfin.org> Thu, 06 Jun 2019 22:45:31 -0400 + jellyfin (10.3.3-1) unstable; urgency=medium * New upstream version 10.3.3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.3 diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.spec b/deployment/fedora-package-x64/pkg-src/jellyfin.spec index e2e814b96..aeea7ecd0 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.spec +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.spec @@ -7,7 +7,7 @@ %endif Name: jellyfin -Version: 10.3.3 +Version: 10.3.5 Release: 1%{?dist} Summary: The Free Software Media Browser License: GPLv2 @@ -140,6 +140,10 @@ fi %systemd_postun_with_restart jellyfin.service %changelog +* Sun Jun 09 2019 Jellyfin Packaging Team <packaging@jellyfin.org> +- New upstream version 10.3.5; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.5 +* Thu Jun 06 2019 Jellyfin Packaging Team <packaging@jellyfin.org> +- New upstream version 10.3.4; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.4 * Fri May 17 2019 Jellyfin Packaging Team <packaging@jellyfin.org> - New upstream version 10.3.3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.3 * Tue Apr 30 2019 Jellyfin Packaging Team <packaging@jellyfin.org> diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 0a60c8c7a..1249a60c0 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -25,10 +25,14 @@ <Rules AnalyzerId="Microsoft.CodeAnalysis.FxCopAnalyzers" RuleNamespace="Microsoft.Design"> <!-- disable warning CA1031: Do not catch general exception types --> <Rule Id="CA1031" Action="Info" /> + <!-- disable warning CA1062: Validate arguments of public methods --> + <Rule Id="CA1062" Action="Info" /> <!-- disable warning CA1822: Member does not access instance data and can be marked as static --> <Rule Id="CA1822" Action="Info" /> <!-- disable warning CA1054: Change the type of parameter url from string to System.Uri --> <Rule Id="CA1054" Action="None" /> + <!-- disable warning CA1303: Do not pass literals as localized parameters --> + <Rule Id="CA1303" Action="None" /> </Rules> </RuleSet> |
