ソースを参照

Merge remote-tracking branch 'upstream/master' into fixes

crobibero 4 年 前
コミット
29d8e38161
28 ファイル変更255 行追加250 行削除
  1. 6 0
      Emby.Dlna/ControlResponse.cs
  2. 11 5
      Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
  3. 2 2
      Emby.Server.Implementations/ConfigurationOptions.cs
  4. 5 43
      Emby.Server.Implementations/Dto/DtoService.cs
  5. 3 2
      Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
  6. 2 2
      Emby.Server.Implementations/HttpServer/StreamWriter.cs
  7. 4 5
      Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
  8. 14 22
      Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
  9. 28 28
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
  10. 4 4
      Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
  11. 17 17
      Emby.Server.Implementations/Localization/Core/bn.json
  12. 21 3
      Emby.Server.Implementations/Localization/Core/ta.json
  13. 6 4
      Emby.Server.Implementations/Serialization/MyXmlSerializer.cs
  14. 6 6
      Jellyfin.Api/Controllers/DlnaServerController.cs
  15. 1 1
      Jellyfin.Api/Controllers/ImageController.cs
  16. 10 11
      Jellyfin.Api/Controllers/LiveTvController.cs
  17. 10 18
      Jellyfin.Api/Controllers/RemoteImageController.cs
  18. 2 2
      Jellyfin.Api/Controllers/VideosController.cs
  19. 1 0
      Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
  20. 31 0
      Jellyfin.Server/Formatters/XmlOutputFormatter.cs
  21. 17 4
      Jellyfin.Server/Program.cs
  22. 3 3
      MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs
  23. 0 24
      MediaBrowser.Controller/Dto/IDtoService.cs
  24. 26 0
      MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
  25. 0 29
      MediaBrowser.Model/Extensions/ListHelper.cs
  26. 7 0
      MediaBrowser.Model/IO/IODefaults.cs
  27. 17 14
      MediaBrowser.Providers/Manager/ImageSaver.cs
  28. 1 1
      MediaBrowser.Providers/Subtitles/SubtitleManager.cs

+ 6 - 0
Emby.Dlna/ControlResponse.cs

@@ -16,5 +16,11 @@ namespace Emby.Dlna
         public string Xml { get; set; }
 
         public bool IsSuccessful { get; set; }
+
+        /// <inheritdoc />
+        public override string ToString()
+        {
+            return Xml;
+        }
     }
 }

+ 11 - 5
Emby.Server.Implementations/AppBase/ConfigurationHelper.cs

@@ -1,3 +1,5 @@
+#nullable enable
+
 using System;
 using System.IO;
 using System.Linq;
@@ -22,7 +24,7 @@ namespace Emby.Server.Implementations.AppBase
         {
             object configuration;
 
-            byte[] buffer = null;
+            byte[]? buffer = null;
 
             // Use try/catch to avoid the extra file system lookup using File.Exists
             try
@@ -36,19 +38,23 @@ namespace Emby.Server.Implementations.AppBase
                 configuration = Activator.CreateInstance(type);
             }
 
-            using var stream = new MemoryStream();
+            using var stream = new MemoryStream(buffer?.Length ?? 0);
             xmlSerializer.SerializeToStream(configuration, stream);
 
             // Take the object we just got and serialize it back to bytes
-            var newBytes = stream.ToArray();
+            byte[] newBytes = stream.GetBuffer();
+            int newBytesLen = (int)stream.Length;
 
             // If the file didn't exist before, or if something has changed, re-save
-            if (buffer == null || !buffer.SequenceEqual(newBytes))
+            if (buffer == null || !newBytes.AsSpan(0, newBytesLen).SequenceEqual(buffer))
             {
                 Directory.CreateDirectory(Path.GetDirectoryName(path));
 
                 // Save it after load in case we got new items
-                File.WriteAllBytes(path, newBytes);
+                using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
+                {
+                    fs.Write(newBytes, 0, newBytesLen);
+                }
             }
 
             return configuration;

+ 2 - 2
Emby.Server.Implementations/ConfigurationOptions.cs

@@ -1,6 +1,5 @@
 using System.Collections.Generic;
 using Emby.Server.Implementations.HttpServer;
-using Emby.Server.Implementations.Updates;
 using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
 
 namespace Emby.Server.Implementations
@@ -19,7 +18,8 @@ namespace Emby.Server.Implementations
             { HttpListenerHost.DefaultRedirectKey, "web/index.html" },
             { FfmpegProbeSizeKey, "1G" },
             { FfmpegAnalyzeDurationKey, "200M" },
-            { PlaylistsAllowDuplicatesKey, bool.TrueString }
+            { PlaylistsAllowDuplicatesKey, bool.TrueString },
+            { BindToUnixSocketKey, bool.FalseString }
         };
     }
 }

+ 5 - 43
Emby.Server.Implementations/Dto/DtoService.cs

@@ -73,25 +73,6 @@ namespace Emby.Server.Implementations.Dto
             _livetvManagerFactory = livetvManagerFactory;
         }
 
-        /// <summary>
-        /// Converts a BaseItem to a DTOBaseItem.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="fields">The fields.</param>
-        /// <param name="user">The user.</param>
-        /// <param name="owner">The owner.</param>
-        /// <returns>Task{DtoBaseItem}.</returns>
-        /// <exception cref="ArgumentNullException">item</exception>
-        public BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, User user = null, BaseItem owner = null)
-        {
-            var options = new DtoOptions
-            {
-                Fields = fields
-            };
-
-            return GetBaseItemDto(item, options, user, owner);
-        }
-
         /// <inheritdoc />
         public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
         {
@@ -443,17 +424,6 @@ namespace Emby.Server.Implementations.Dto
             return folder.GetChildCount(user);
         }
 
-        /// <summary>
-        /// Gets client-side Id of a server-side BaseItem.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>System.String.</returns>
-        /// <exception cref="ArgumentNullException">item</exception>
-        public string GetDtoId(BaseItem item)
-        {
-            return item.Id.ToString("N", CultureInfo.InvariantCulture);
-        }
-
         private static void SetBookProperties(BaseItemDto dto, Book item)
         {
             dto.SeriesName = item.SeriesName;
@@ -484,6 +454,11 @@ namespace Emby.Server.Implementations.Dto
             }
         }
 
+        private string GetDtoId(BaseItem item)
+        {
+            return item.Id.ToString("N", CultureInfo.InvariantCulture);
+        }
+
         private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item)
         {
             if (!string.IsNullOrEmpty(item.Album))
@@ -513,19 +488,6 @@ namespace Emby.Server.Implementations.Dto
                 .ToArray();
         }
 
-        private string GetImageCacheTag(BaseItem item, ImageType type)
-        {
-            try
-            {
-                return _imageProcessor.GetImageCacheTag(item, type);
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Error getting {type} image info", type);
-                return null;
-            }
-        }
-
         private string GetImageCacheTag(BaseItem item, ItemImageInfo image)
         {
             try

+ 3 - 2
Emby.Server.Implementations/HttpServer/HttpResultFactory.cs

@@ -105,7 +105,7 @@ namespace Emby.Server.Implementations.HttpServer
                 responseHeaders = new Dictionary<string, string>();
             }
 
-            if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string expires))
+            if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out _))
             {
                 responseHeaders[HeaderNames.Expires] = "0";
             }
@@ -326,7 +326,8 @@ namespace Emby.Server.Implementations.HttpServer
             return GetHttpResult(request, ms, contentType, true, responseHeaders);
         }
 
-        private IHasHeaders GetCompressedResult(byte[] content,
+        private IHasHeaders GetCompressedResult(
+            byte[] content,
             string requestedCompressionType,
             IDictionary<string, string> responseHeaders,
             bool isHeadRequest,

+ 2 - 2
Emby.Server.Implementations/HttpServer/StreamWriter.cs

@@ -95,13 +95,13 @@ namespace Emby.Server.Implementations.HttpServer
 
                 if (bytes != null)
                 {
-                    await responseStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+                    await responseStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
                 }
                 else
                 {
                     using (var src = SourceStream)
                     {
-                        await src.CopyToAsync(responseStream).ConfigureAwait(false);
+                        await src.CopyToAsync(responseStream, cancellationToken).ConfigureAwait(false);
                     }
                 }
             }

+ 4 - 5
Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs

@@ -19,8 +19,7 @@ namespace Emby.Server.Implementations.LiveTv
     public class LiveTvMediaSourceProvider : IMediaSourceProvider
     {
         // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
-        private const char StreamIdDelimeter = '_';
-        private const string StreamIdDelimeterString = "_";
+        private const char StreamIdDelimiter = '_';
 
         private readonly ILiveTvManager _liveTvManager;
         private readonly ILogger<LiveTvMediaSourceProvider> _logger;
@@ -47,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv
                 }
             }
 
-            return Task.FromResult<IEnumerable<MediaSourceInfo>>(Array.Empty<MediaSourceInfo>());
+            return Task.FromResult(Enumerable.Empty<MediaSourceInfo>());
         }
 
         private async Task<IEnumerable<MediaSourceInfo>> GetMediaSourcesInternal(BaseItem item, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
@@ -98,7 +97,7 @@ namespace Emby.Server.Implementations.LiveTv
                         source.Id ?? string.Empty
                     };
 
-                    source.OpenToken = string.Join(StreamIdDelimeterString, openKeys);
+                    source.OpenToken = string.Join(StreamIdDelimiter, openKeys);
                 }
 
                 // Dummy this up so that direct play checks can still run
@@ -116,7 +115,7 @@ namespace Emby.Server.Implementations.LiveTv
         /// <inheritdoc />
         public async Task<ILiveStream> OpenMediaSource(string openToken, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
         {
-            var keys = openToken.Split(new[] { StreamIdDelimeter }, 3);
+            var keys = openToken.Split(StreamIdDelimiter, 3);
             var mediaSourceId = keys.Length >= 3 ? keys[2] : null;
 
             var info = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, currentLiveStreams, cancellationToken).ConfigureAwait(false);

+ 14 - 22
Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs

@@ -1,10 +1,10 @@
 #pragma warning disable CS1591
 
 using System;
-using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Configuration;
@@ -14,7 +14,7 @@ using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.LiveTv;
-using MediaBrowser.Model.Serialization;
+using Microsoft.Extensions.Caching.Memory;
 using Microsoft.Extensions.Logging;
 
 namespace Emby.Server.Implementations.LiveTv.TunerHosts
@@ -23,17 +23,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
     {
         protected readonly IServerConfigurationManager Config;
         protected readonly ILogger<BaseTunerHost> Logger;
-        protected IJsonSerializer JsonSerializer;
         protected readonly IFileSystem FileSystem;
 
-        private readonly ConcurrentDictionary<string, ChannelCache> _channelCache =
-            new ConcurrentDictionary<string, ChannelCache>(StringComparer.OrdinalIgnoreCase);
+        private readonly IMemoryCache _memoryCache;
 
-        protected BaseTunerHost(IServerConfigurationManager config, ILogger<BaseTunerHost> logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem)
+        protected BaseTunerHost(IServerConfigurationManager config, ILogger<BaseTunerHost> logger, IFileSystem fileSystem, IMemoryCache memoryCache)
         {
             Config = config;
             Logger = logger;
-            JsonSerializer = jsonSerializer;
+            _memoryCache = memoryCache;
             FileSystem = fileSystem;
         }
 
@@ -44,23 +42,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 
         public async Task<List<ChannelInfo>> GetChannels(TunerHostInfo tuner, bool enableCache, CancellationToken cancellationToken)
         {
-            ChannelCache cache = null;
             var key = tuner.Id;
 
-            if (enableCache && !string.IsNullOrEmpty(key) && _channelCache.TryGetValue(key, out cache))
+            if (enableCache && !string.IsNullOrEmpty(key) && _memoryCache.TryGetValue(key, out List<ChannelInfo> cache))
             {
-                return cache.Channels.ToList();
+                return cache;
             }
 
-            var result = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false);
-            var list = result.ToList();
+            var list = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false);
             // logger.LogInformation("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list));
 
             if (!string.IsNullOrEmpty(key) && list.Count > 0)
             {
-                cache = cache ?? new ChannelCache();
-                cache.Channels = list;
-                _channelCache.AddOrUpdate(key, cache, (k, v) => cache);
+                _memoryCache.Set(key, list);
             }
 
             return list;
@@ -95,7 +89,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
                         try
                         {
                             Directory.CreateDirectory(Path.GetDirectoryName(channelCacheFile));
-                            JsonSerializer.SerializeToFile(channels, channelCacheFile);
+                            await using var writeStream = File.OpenWrite(channelCacheFile);
+                            await JsonSerializer.SerializeAsync(writeStream, channels, cancellationToken: cancellationToken).ConfigureAwait(false);
                         }
                         catch (IOException)
                         {
@@ -110,7 +105,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
                     {
                         try
                         {
-                            var channels = JsonSerializer.DeserializeFromFile<List<ChannelInfo>>(channelCacheFile);
+                            await using var readStream = File.OpenRead(channelCacheFile);
+                            var channels = await JsonSerializer.DeserializeAsync<List<ChannelInfo>>(readStream, cancellationToken: cancellationToken)
+                                .ConfigureAwait(false);
                             list.AddRange(channels);
                         }
                         catch (IOException)
@@ -233,10 +230,5 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
         {
             return Config.GetConfiguration<LiveTvOptions>("livetv");
         }
-
-        private class ChannelCache
-        {
-            public List<ChannelInfo> Channels;
-        }
     }
 }

+ 28 - 28
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs

@@ -7,6 +7,7 @@ using System.IO;
 using System.Linq;
 using System.Net;
 using System.Net.Http;
+using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Configuration;
@@ -23,7 +24,7 @@ using MediaBrowser.Model.IO;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Serialization;
+using Microsoft.Extensions.Caching.Memory;
 using Microsoft.Extensions.Logging;
 
 namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
@@ -39,14 +40,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
         public HdHomerunHost(
             IServerConfigurationManager config,
             ILogger<HdHomerunHost> logger,
-            IJsonSerializer jsonSerializer,
             IFileSystem fileSystem,
             IHttpClient httpClient,
             IServerApplicationHost appHost,
             ISocketFactory socketFactory,
             INetworkManager networkManager,
-            IStreamHelper streamHelper)
-            : base(config, logger, jsonSerializer, fileSystem)
+            IStreamHelper streamHelper,
+            IMemoryCache memoryCache)
+            : base(config, logger, fileSystem, memoryCache)
         {
             _httpClient = httpClient;
             _appHost = appHost;
@@ -75,18 +76,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 BufferContent = false
             };
 
-            using (var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false))
-            using (var stream = response.Content)
-            {
-                var lineup = await JsonSerializer.DeserializeFromStreamAsync<List<Channels>>(stream).ConfigureAwait(false) ?? new List<Channels>();
-
-                if (info.ImportFavoritesOnly)
-                {
-                    lineup = lineup.Where(i => i.Favorite).ToList();
-                }
+            using var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false);
+            await using var stream = response.Content;
+            var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, cancellationToken: cancellationToken)
+                .ConfigureAwait(false) ?? new List<Channels>();
 
-                return lineup.Where(i => !i.DRM).ToList();
+            if (info.ImportFavoritesOnly)
+            {
+                lineup = lineup.Where(i => i.Favorite).ToList();
             }
+
+            return lineup.Where(i => !i.DRM).ToList();
         }
 
         private class HdHomerunChannelInfo : ChannelInfo
@@ -132,30 +132,30 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 
             try
             {
-                using (var response = await _httpClient.SendAsync(new HttpRequestOptions()
+                using var response = await _httpClient.SendAsync(
+                    new HttpRequestOptions
                 {
-                    Url = string.Format("{0}/discover.json", GetApiUrl(info)),
+                    Url = string.Format(CultureInfo.InvariantCulture, "{0}/discover.json", GetApiUrl(info)),
                     CancellationToken = cancellationToken,
                     BufferContent = false
-                }, HttpMethod.Get).ConfigureAwait(false))
-                using (var stream = response.Content)
-                {
-                    var discoverResponse = await JsonSerializer.DeserializeFromStreamAsync<DiscoverResponse>(stream).ConfigureAwait(false);
+                }, HttpMethod.Get).ConfigureAwait(false);
+                await using var stream = response.Content;
+                var discoverResponse = await JsonSerializer.DeserializeAsync<DiscoverResponse>(stream, cancellationToken: cancellationToken)
+                    .ConfigureAwait(false);
 
-                    if (!string.IsNullOrEmpty(cacheKey))
+                if (!string.IsNullOrEmpty(cacheKey))
+                {
+                    lock (_modelCache)
                     {
-                        lock (_modelCache)
-                        {
-                            _modelCache[cacheKey] = discoverResponse;
-                        }
+                        _modelCache[cacheKey] = discoverResponse;
                     }
-
-                    return discoverResponse;
                 }
+
+                return discoverResponse;
             }
             catch (HttpException ex)
             {
-                if (!throwAllExceptions && ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
+                if (!throwAllExceptions && ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
                 {
                     var defaultValue = "HDHR";
                     var response = new DiscoverResponse

+ 4 - 4
Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs

@@ -18,7 +18,7 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
+using Microsoft.Extensions.Caching.Memory;
 using Microsoft.Extensions.Logging;
 using Microsoft.Net.Http.Headers;
 
@@ -36,13 +36,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             IServerConfigurationManager config,
             IMediaSourceManager mediaSourceManager,
             ILogger<M3UTunerHost> logger,
-            IJsonSerializer jsonSerializer,
             IFileSystem fileSystem,
             IHttpClient httpClient,
             IServerApplicationHost appHost,
             INetworkManager networkManager,
-            IStreamHelper streamHelper)
-            : base(config, logger, jsonSerializer, fileSystem)
+            IStreamHelper streamHelper,
+            IMemoryCache memoryCache)
+            : base(config, logger, fileSystem, memoryCache)
         {
             _httpClient = httpClient;
             _appHost = appHost;

+ 17 - 17
Emby.Server.Implementations/Localization/Core/bn.json

@@ -1,12 +1,12 @@
 {
     "DeviceOnlineWithName": "{0}-এর সাথে সংযুক্ত হয়েছে",
     "DeviceOfflineWithName": "{0}-এর সাথে সংযোগ বিচ্ছিন্ন হয়েছে",
-    "Collections": "সংকলন",
+    "Collections": "কলেক্শন",
     "ChapterNameValue": "অধ্যায় {0}",
     "Channels": "চ্যানেল",
-    "CameraImageUploadedFrom": "একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে {0} থেকে",
+    "CameraImageUploadedFrom": "{0} থেকে একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে",
     "Books": "বই",
-    "AuthenticationSucceededWithUserName": "{0} যাচাই সফল",
+    "AuthenticationSucceededWithUserName": "{0} অনুমোদন সফল",
     "Artists": "শিল্পীরা",
     "Application": "অ্যাপ্লিকেশন",
     "Albums": "অ্যালবামগুলো",
@@ -14,13 +14,13 @@
     "HeaderFavoriteArtists": "প্রিয় শিল্পীরা",
     "HeaderFavoriteAlbums": "প্রিয় এলবামগুলো",
     "HeaderContinueWatching": "দেখতে থাকুন",
-    "HeaderCameraUploads": "ক্যামেরার আপলোডগুলো",
-    "HeaderAlbumArtists": "এলবামের শিল্পী",
-    "Genres": "ঘরানা",
+    "HeaderCameraUploads": "ক্যামেরার আপলোড সমূহ",
+    "HeaderAlbumArtists": "এলবাম শিল্পী",
+    "Genres": "জেনার",
     "Folders": "ফোল্ডারগুলো",
-    "Favorites": "ফেভারিটগুলো",
+    "Favorites": "পছন্দসমূহ",
     "FailedLoginAttemptWithUserName": "{0} লগিন করতে ব্যর্থ হয়েছে",
-    "AppDeviceValues": "প: {0}, ডিভাইস: {0}",
+    "AppDeviceValues": "অ্যাপ: {0}, ডিভাইস: {0}",
     "VersionNumber": "সংস্করণ {0}",
     "ValueSpecialEpisodeName": "বিশেষ - {0}",
     "ValueHasBeenAddedToLibrary": "আপনার লাইব্রেরিতে {0} যোগ করা হয়েছে",
@@ -74,20 +74,20 @@
     "NameInstallFailed": "{0} ইন্সটল ব্যর্থ",
     "MusicVideos": "গানের ভিডিও",
     "Music": "গান",
-    "Movies": "সিনেমা",
+    "Movies": "চলচ্চিত্র",
     "MixedContent": "মিশ্র কন্টেন্ট",
-    "MessageServerConfigurationUpdated": "সার্ভারের কনফিগারেশন হালনাগাদ করা হয়েছে",
-    "HeaderRecordingGroups": "রেকর্ডিং গ্রুপ",
-    "MessageNamedServerConfigurationUpdatedWithValue": "সার্ভারের {0} কনফিগারেসন অংশ আপডেট করা হয়েছে",
-    "MessageApplicationUpdatedTo": "জেলিফিন সার্ভার {0} তে হালনাগাদ করা হয়েছে",
-    "MessageApplicationUpdated": "জেলিফিন সার্ভার হালনাগাদ করা হয়েছে",
-    "Latest": "একদম নতুন",
+    "MessageServerConfigurationUpdated": "সার্ভারের কনফিগারেশন আপডেট করা হয়েছে",
+    "HeaderRecordingGroups": "রেকর্ডিং দল",
+    "MessageNamedServerConfigurationUpdatedWithValue": "সার্ভারের {0} কনফিগারেসনের অংশ আপডেট করা হয়েছে",
+    "MessageApplicationUpdatedTo": "জেলিফিন সার্ভার {0} তে আপডেট করা হয়েছে",
+    "MessageApplicationUpdated": "জেলিফিন সার্ভার আপডেট করা হয়েছে",
+    "Latest": "সর্বশেষ",
     "LabelRunningTimeValue": "চলার সময়: {0}",
-    "LabelIpAddressValue": "আইপি ঠিকানা: {0}",
+    "LabelIpAddressValue": "আইপি এড্রেস: {0}",
     "ItemRemovedWithName": "{0} লাইব্রেরি থেকে বাদ দেয়া হয়েছে",
     "ItemAddedWithName": "{0} লাইব্রেরিতে যোগ করা হয়েছে",
     "Inherit": "থেকে পাওয়া",
-    "HomeVideos": "বাসার ভিডিও",
+    "HomeVideos": "হোম ভিডিও",
     "HeaderNextUp": "এরপরে আসছে",
     "HeaderLiveTV": "লাইভ টিভি",
     "HeaderFavoriteSongs": "প্রিয় গানগুলো",

+ 21 - 3
Emby.Server.Implementations/Localization/Core/ta.json

@@ -45,7 +45,7 @@
     "TvShows": "தொலைக்காட்சித் தொடர்கள்",
     "Sync": "ஒத்திசைவு",
     "StartupEmbyServerIsLoading": "ஜெல்லிஃபின் சேவையகம் துவங்குகிறது. சிறிது நேரம் கழித்து முயற்சிக்கவும்.",
-    "Songs": "பாட்டுகள்",
+    "Songs": "பாட்கள்",
     "Shows": "தொடர்கள்",
     "ServerNameNeedsToBeRestarted": "{0} மறுதொடக்கம் செய்யப்பட வேண்டும்",
     "ScheduledTaskStartedWithName": "{0} துவங்கியது",
@@ -93,7 +93,25 @@
     "Channels": "சேனல்கள்",
     "Books": "புத்தகங்கள்",
     "AuthenticationSucceededWithUserName": "{0} வெற்றிகரமாக அங்கீகரிக்கப்பட்டது",
-    "Artists": "கலைஞர்கள்",
+    "Artists": "கலைஞர்",
     "Application": "செயலி",
-    "Albums": "ஆல்பங்கள்"
+    "Albums": "ஆல்பங்கள்",
+    "NewVersionIsAvailable": "ஜெல்லிஃபின் சேவையகத்தின் புதிய பதிப்பு பதிவிறக்கத்திற்கு கிடைக்கிறது.",
+    "MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0 புதுப்பிக்கப்பட்டது",
+    "TaskCleanCacheDescription": "கணினிக்கு இனி தேவைப்படாத தற்காலிக கோப்புகளை நீக்கு.",
+    "UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது",
+    "SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0 } இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன",
+    "TaskDownloadMissingSubtitlesDescription": "மெட்டாடேட்டா உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.",
+    "TaskCleanTranscodeDescription": "டிரான்ஸ்கோட் கோப்புகளை ஒரு நாளுக்கு மேல் பழையதாக நீக்குகிறது.",
+    "TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட செருகுநிரல்களுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.",
+    "TaskRefreshPeopleDescription": "உங்கள் மீடியா நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மெட்டாடேட்டாவை புதுப்பிக்கும்.",
+    "TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.",
+    "TaskCleanLogs": "பதிவு அடைவு சுத்தம் செய்யுங்கள்",
+    "TaskRefreshLibraryDescription": "புதிய கோப்புகளுக்காக உங்கள் மீடியா நூலகத்தை ஸ்கேன் செய்து மீத்தரவை புதுப்பிக்கும்.",
+    "TaskRefreshChapterImagesDescription": "அத்தியாயங்களைக் கொண்ட வீடியோக்களுக்கான சிறு உருவங்களை உருவாக்குகிறது.",
+    "ValueHasBeenAddedToLibrary": "உங்கள் மீடியா நூலகத்தில் {0} சேர்க்கப்பட்டது",
+    "UserOnlineFromDevice": "{1} இருந்து {0} ஆன்லைன்",
+    "HomeVideos": "முகப்பு வீடியோக்கள்",
+    "UserStoppedPlayingItemWithValues": "{2} இல் {1} முடித்துவிட்டது",
+    "UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது"
 }

+ 6 - 4
Emby.Server.Implementations/Serialization/MyXmlSerializer.cs

@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
 using System.IO;
 using System.Xml;
 using System.Xml.Serialization;
+using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Serialization;
 
 namespace Emby.Server.Implementations.Serialization
@@ -53,10 +54,11 @@ namespace Emby.Server.Implementations.Serialization
         /// <param name="stream">The stream.</param>
         public void SerializeToStream(object obj, Stream stream)
         {
-            using (var writer = new XmlTextWriter(stream, null))
+            using (var writer = new StreamWriter(stream, null, IODefaults.StreamWriterBufferSize, true))
+            using (var textWriter = new XmlTextWriter(writer))
             {
-                writer.Formatting = Formatting.Indented;
-                SerializeToWriter(obj, writer);
+                textWriter.Formatting = Formatting.Indented;
+                SerializeToWriter(obj, textWriter);
             }
         }
 
@@ -95,7 +97,7 @@ namespace Emby.Server.Implementations.Serialization
         /// <returns>System.Object.</returns>
         public object DeserializeFromBytes(Type type, byte[] buffer)
         {
-            using (var stream = new MemoryStream(buffer))
+            using (var stream = new MemoryStream(buffer, 0, buffer.Length, false, true))
             {
                 return DeserializeFromStream(type, stream);
             }

+ 6 - 6
Jellyfin.Api/Controllers/DlnaServerController.cs

@@ -60,8 +60,8 @@ namespace Jellyfin.Api.Controllers
         /// <param name="serverId">Server UUID.</param>
         /// <response code="200">Dlna content directory returned.</response>
         /// <returns>An <see cref="OkResult"/> containing the dlna content directory xml.</returns>
-        [HttpGet("{serverId}/ContentDirectory/ContentDirectory")]
-        [HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_2")]
+        [HttpGet("{serverId}/ContentDirectory")]
+        [HttpGet("{serverId}/ContentDirectory.xml", Name = "GetContentDirectory_2")]
         [Produces(XMLContentType)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
@@ -75,8 +75,8 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// <param name="serverId">Server UUID.</param>
         /// <returns>Dlna media receiver registrar xml.</returns>
-        [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar")]
-        [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_2")]
+        [HttpGet("{serverId}/MediaReceiverRegistrar")]
+        [HttpGet("{serverId}/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_2")]
         [Produces(XMLContentType)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
@@ -90,8 +90,8 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// <param name="serverId">Server UUID.</param>
         /// <returns>Dlna media receiver registrar xml.</returns>
-        [HttpGet("{serverId}/ConnectionManager/ConnectionManager")]
-        [HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_2")]
+        [HttpGet("{serverId}/ConnectionManager")]
+        [HttpGet("{serverId}/ConnectionManager.xml", Name = "GetConnectionManager_2")]
         [Produces(XMLContentType)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]

+ 1 - 1
Jellyfin.Api/Controllers/ImageController.cs

@@ -969,7 +969,7 @@ namespace Jellyfin.Api.Controllers
             var text = await reader.ReadToEndAsync().ConfigureAwait(false);
 
             var bytes = Convert.FromBase64String(text);
-            return new MemoryStream(bytes) { Position = 0 };
+            return new MemoryStream(bytes, 0, bytes.Length, false, true);
         }
 
         private ImageInfo? GetImageInfo(BaseItem item, ItemImageInfo info, int? imageIndex)

+ 10 - 11
Jellyfin.Api/Controllers/LiveTvController.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Linq;
+using System.Net.Http;
 using System.Net.Mime;
 using System.Security.Cryptography;
 using System.Text;
@@ -15,7 +16,6 @@ using Jellyfin.Api.Models.LiveTvDtos;
 using Jellyfin.Data.Enums;
 using MediaBrowser.Common;
 using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
@@ -39,7 +39,7 @@ namespace Jellyfin.Api.Controllers
     {
         private readonly ILiveTvManager _liveTvManager;
         private readonly IUserManager _userManager;
-        private readonly IHttpClient _httpClient;
+        private readonly IHttpClientFactory _httpClientFactory;
         private readonly ILibraryManager _libraryManager;
         private readonly IDtoService _dtoService;
         private readonly ISessionContext _sessionContext;
@@ -52,7 +52,7 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// <param name="liveTvManager">Instance of the <see cref="ILiveTvManager"/> interface.</param>
         /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
-        /// <param name="httpClient">Instance of the <see cref="IHttpClient"/> interface.</param>
+        /// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
         /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
         /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
         /// <param name="sessionContext">Instance of the <see cref="ISessionContext"/> interface.</param>
@@ -62,7 +62,7 @@ namespace Jellyfin.Api.Controllers
         public LiveTvController(
             ILiveTvManager liveTvManager,
             IUserManager userManager,
-            IHttpClient httpClient,
+            IHttpClientFactory httpClientFactory,
             ILibraryManager libraryManager,
             IDtoService dtoService,
             ISessionContext sessionContext,
@@ -72,7 +72,7 @@ namespace Jellyfin.Api.Controllers
         {
             _liveTvManager = liveTvManager;
             _userManager = userManager;
-            _httpClient = httpClient;
+            _httpClientFactory = httpClientFactory;
             _libraryManager = libraryManager;
             _dtoService = dtoService;
             _sessionContext = sessionContext;
@@ -1069,13 +1069,12 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult> GetSchedulesDirectCountries()
         {
+            var client = _httpClientFactory.CreateClient();
             // https://json.schedulesdirect.org/20141201/available/countries
-            var response = await _httpClient.Get(new HttpRequestOptions
-            {
-                Url = "https://json.schedulesdirect.org/20141201/available/countries",
-                BufferContent = false
-            }).ConfigureAwait(false);
-            return File(response, MediaTypeNames.Application.Json);
+            using var response = await client.GetAsync("https://json.schedulesdirect.org/20141201/available/countries")
+                .ConfigureAwait(false);
+
+            return File(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), MediaTypeNames.Application.Json);
         }
 
         /// <summary>

+ 10 - 18
Jellyfin.Api/Controllers/RemoteImageController.cs

@@ -3,12 +3,12 @@ using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.IO;
 using System.Linq;
+using System.Net.Http;
 using System.Net.Mime;
 using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
@@ -30,7 +30,7 @@ namespace Jellyfin.Api.Controllers
     {
         private readonly IProviderManager _providerManager;
         private readonly IServerApplicationPaths _applicationPaths;
-        private readonly IHttpClient _httpClient;
+        private readonly IHttpClientFactory _httpClientFactory;
         private readonly ILibraryManager _libraryManager;
 
         /// <summary>
@@ -38,17 +38,17 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
         /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param>
-        /// <param name="httpClient">Instance of the <see cref="IHttpClient"/> interface.</param>
+        /// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
         /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
         public RemoteImageController(
             IProviderManager providerManager,
             IServerApplicationPaths applicationPaths,
-            IHttpClient httpClient,
+            IHttpClientFactory httpClientFactory,
             ILibraryManager libraryManager)
         {
             _providerManager = providerManager;
             _applicationPaths = applicationPaths;
-            _httpClient = httpClient;
+            _httpClientFactory = httpClientFactory;
             _libraryManager = libraryManager;
         }
 
@@ -244,22 +244,14 @@ namespace Jellyfin.Api.Controllers
         /// <returns>Task.</returns>
         private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
         {
-            using var result = await _httpClient.GetResponse(new HttpRequestOptions
-            {
-                Url = url,
-                BufferContent = false
-            }).ConfigureAwait(false);
-            var ext = result.ContentType.Split('/').Last();
-
+            var httpClient = _httpClientFactory.CreateClient();
+            using var response = await httpClient.GetAsync(url).ConfigureAwait(false);
+            var ext = response.Content.Headers.ContentType.MediaType.Split('/').Last();
             var fullCachePath = GetFullCachePath(urlHash + "." + ext);
 
             Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
-            await using (var stream = result.Content)
-            {
-                await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
-                await stream.CopyToAsync(fileStream).ConfigureAwait(false);
-            }
-
+            await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
+            await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
             Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
             await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath, CancellationToken.None)
                 .ConfigureAwait(false);

+ 2 - 2
Jellyfin.Api/Controllers/VideosController.cs

@@ -470,8 +470,8 @@ namespace Jellyfin.Api.Controllers
             {
                 StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
 
-                using var httpClient = _httpClientFactory.CreateClient();
-                return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, HttpContext).ConfigureAwait(false);
+                var httpClient = _httpClientFactory.CreateClient();
+                return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, this, httpClient).ConfigureAwait(false);
             }
 
             if (@static.HasValue && @static.Value && state.InputProtocol != MediaProtocol.File)

+ 1 - 0
Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs

@@ -154,6 +154,7 @@ namespace Jellyfin.Server.Extensions
                     opts.OutputFormatters.Insert(0, new PascalCaseJsonProfileFormatter());
 
                     opts.OutputFormatters.Add(new CssOutputFormatter());
+                    opts.OutputFormatters.Add(new XmlOutputFormatter());
                 })
 
                 // Clear app parts to avoid other assemblies being picked up

+ 31 - 0
Jellyfin.Server/Formatters/XmlOutputFormatter.cs

@@ -0,0 +1,31 @@
+using System.Net.Mime;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Formatters;
+
+namespace Jellyfin.Server.Formatters
+{
+    /// <summary>
+    /// Xml output formatter.
+    /// </summary>
+    public class XmlOutputFormatter : TextOutputFormatter
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="XmlOutputFormatter"/> class.
+        /// </summary>
+        public XmlOutputFormatter()
+        {
+            SupportedMediaTypes.Add(MediaTypeNames.Text.Xml);
+            SupportedMediaTypes.Add("text/xml;charset=UTF-8");
+            SupportedEncodings.Add(Encoding.UTF8);
+            SupportedEncodings.Add(Encoding.Unicode);
+        }
+
+        /// <inheritdoc />
+        public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
+        {
+            return context.HttpContext.Response.WriteAsync(context.Object?.ToString());
+        }
+    }
+}

+ 17 - 4
Jellyfin.Server/Program.cs

@@ -344,11 +344,24 @@ namespace Jellyfin.Server
                         }
                     }
 
-                    // Bind to unix socket (only on OSX and Linux)
-                    if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+                    // Bind to unix socket (only on macOS and Linux)
+                    if (startupConfig.UseUnixSocket() && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                     {
-                        // TODO: allow configuration of socket path
-                        var socketPath = $"{appPaths.DataPath}/socket.sock";
+                        var socketPath = startupConfig.GetUnixSocketPath();
+                        if (string.IsNullOrEmpty(socketPath))
+                        {
+                            var xdgRuntimeDir = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR");
+                            if (xdgRuntimeDir == null)
+                            {
+                                // Fall back to config dir
+                                socketPath = Path.Join(appPaths.ConfigurationDirectoryPath, "socket.sock");
+                            }
+                            else
+                            {
+                                socketPath = Path.Join(xdgRuntimeDir, "jellyfin-socket");
+                            }
+                        }
+
                         // Workaround for https://github.com/aspnet/AspNetCore/issues/14134
                         if (File.Exists(socketPath))
                         {

+ 3 - 3
MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs

@@ -8,7 +8,7 @@ using System.Text.Json.Serialization;
 namespace MediaBrowser.Common.Json.Converters
 {
     /// <summary>
-    /// Long to String JSON converter.
+    /// Parse JSON string as long.
     /// Javascript does not support 64-bit integers.
     /// </summary>
     public class JsonInt64Converter : JsonConverter<long>
@@ -43,14 +43,14 @@ namespace MediaBrowser.Common.Json.Converters
         }
 
         /// <summary>
-        /// Write long to JSON string.
+        /// Write long to JSON long.
         /// </summary>
         /// <param name="writer"><see cref="Utf8JsonWriter"/>.</param>
         /// <param name="value">Value to write.</param>
         /// <param name="options">Options.</param>
         public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
         {
-            writer.WriteStringValue(value.ToString(NumberFormatInfo.InvariantInfo));
+            writer.WriteNumberValue(value);
         }
     }
 }

+ 0 - 24
MediaBrowser.Controller/Dto/IDtoService.cs

@@ -2,7 +2,6 @@ using System.Collections.Generic;
 using Jellyfin.Data.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Querying;
 
 namespace MediaBrowser.Controller.Dto
 {
@@ -11,20 +10,6 @@ namespace MediaBrowser.Controller.Dto
     /// </summary>
     public interface IDtoService
     {
-        /// <summary>
-        /// Gets the dto id.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>System.String.</returns>
-        string GetDtoId(BaseItem item);
-
-        /// <summary>
-        /// Attaches the primary image aspect ratio.
-        /// </summary>
-        /// <param name="dto">The dto.</param>
-        /// <param name="item">The item.</param>
-        void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item);
-
         /// <summary>
         /// Gets the primary image aspect ratio.
         /// </summary>
@@ -32,15 +17,6 @@ namespace MediaBrowser.Controller.Dto
         /// <returns>System.Nullable&lt;System.Double&gt;.</returns>
         double? GetPrimaryImageAspectRatio(BaseItem item);
 
-        /// <summary>
-        /// Gets the base item dto.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="fields">The fields.</param>
-        /// <param name="user">The user.</param>
-        /// <param name="owner">The owner.</param>
-        BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, User user = null, BaseItem owner = null);
-
         /// <summary>
         /// Gets the base item dto.
         /// </summary>

+ 26 - 0
MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs

@@ -33,6 +33,16 @@ namespace MediaBrowser.Controller.Extensions
         /// </summary>
         public const string PlaylistsAllowDuplicatesKey = "playlists:allowDuplicates";
 
+        /// <summary>
+        /// The key for a setting that indicates whether kestrel should bind to a unix socket.
+        /// </summary>
+        public const string BindToUnixSocketKey = "kestrel:socket";
+
+        /// <summary>
+        /// The key for the unix socket path.
+        /// </summary>
+        public const string UnixSocketPathKey = "kestrel:socketPath";
+
         /// <summary>
         /// Gets a value indicating whether the application should host static web content from the <see cref="IConfiguration"/>.
         /// </summary>
@@ -65,5 +75,21 @@ namespace MediaBrowser.Controller.Extensions
         /// <returns>True if playlists should allow duplicates, otherwise false.</returns>
         public static bool DoPlaylistsAllowDuplicates(this IConfiguration configuration)
             => configuration.GetValue<bool>(PlaylistsAllowDuplicatesKey);
+
+        /// <summary>
+        /// Gets a value indicating whether kestrel should bind to a unix socket from the <see cref="IConfiguration" />.
+        /// </summary>
+        /// <param name="configuration">The configuration to read the setting from.</param>
+        /// <returns><c>true</c> if kestrel should bind to a unix socket, otherwise <c>false</c>.</returns>
+        public static bool UseUnixSocket(this IConfiguration configuration)
+            => configuration.GetValue<bool>(BindToUnixSocketKey);
+
+        /// <summary>
+        /// Gets the path for the unix socket from the <see cref="IConfiguration" />.
+        /// </summary>
+        /// <param name="configuration">The configuration to read the setting from.</param>
+        /// <returns>The unix socket path.</returns>
+        public static string GetUnixSocketPath(this IConfiguration configuration)
+            => configuration[UnixSocketPathKey];
     }
 }

+ 0 - 29
MediaBrowser.Model/Extensions/ListHelper.cs

@@ -1,29 +0,0 @@
-#nullable disable
-#pragma warning disable CS1591
-
-using System;
-
-namespace MediaBrowser.Model.Extensions
-{
-    // TODO: @bond remove
-    public static class ListHelper
-    {
-        public static bool ContainsIgnoreCase(string[] list, string value)
-        {
-            if (value == null)
-            {
-                throw new ArgumentNullException(nameof(value));
-            }
-
-            foreach (var item in list)
-            {
-                if (string.Equals(item, value, StringComparison.OrdinalIgnoreCase))
-                {
-                    return true;
-                }
-            }
-
-            return false;
-        }
-    }
-}

+ 7 - 0
MediaBrowser.Model/IO/IODefaults.cs

@@ -1,3 +1,5 @@
+using System.IO;
+
 namespace MediaBrowser.Model.IO
 {
     /// <summary>
@@ -14,5 +16,10 @@ namespace MediaBrowser.Model.IO
         /// The default file stream buffer size.
         /// </summary>
         public const int FileStreamBufferSize = 4096;
+
+        /// <summary>
+        /// The default <see cref="StreamWriter" /> buffer size.
+        /// </summary>
+        public const int StreamWriterBufferSize = 1024;
     }
 }

+ 17 - 14
MediaBrowser.Providers/Manager/ImageSaver.cs

@@ -124,13 +124,16 @@ namespace MediaBrowser.Providers.Manager
             var retryPaths = GetSavePaths(item, type, imageIndex, mimeType, false);
 
             // If there are more than one output paths, the stream will need to be seekable
-            var memoryStream = new MemoryStream();
-            await using (source.ConfigureAwait(false))
+            if (paths.Length > 1 && !source.CanSeek)
             {
-                await source.CopyToAsync(memoryStream).ConfigureAwait(false);
-            }
+                var memoryStream = new MemoryStream();
+                await using (source.ConfigureAwait(false))
+                {
+                    await source.CopyToAsync(memoryStream).ConfigureAwait(false);
+                }
 
-            source = memoryStream;
+                source = memoryStream;
+            }
 
             var currentImage = GetCurrentImage(item, type, index);
             var currentImageIsLocalFile = currentImage != null && currentImage.IsLocalFile;
@@ -140,20 +143,21 @@ namespace MediaBrowser.Providers.Manager
 
             await using (source.ConfigureAwait(false))
             {
-                var currentPathIndex = 0;
-
-                foreach (var path in paths)
+                for (int i = 0; i < paths.Length; i++)
                 {
-                    source.Position = 0;
+                    if (i != 0)
+                    {
+                        source.Position = 0;
+                    }
+
                     string retryPath = null;
                     if (paths.Length == retryPaths.Length)
                     {
-                        retryPath = retryPaths[currentPathIndex];
+                        retryPath = retryPaths[i];
                     }
 
-                    var savedPath = await SaveImageToLocation(source, path, retryPath, cancellationToken).ConfigureAwait(false);
+                    var savedPath = await SaveImageToLocation(source, paths[i], retryPath, cancellationToken).ConfigureAwait(false);
                     savedPaths.Add(savedPath);
-                    currentPathIndex++;
                 }
             }
 
@@ -224,7 +228,6 @@ namespace MediaBrowser.Providers.Manager
                 }
             }
 
-            source.Position = 0;
             await SaveImageToLocation(source, retryPath, cancellationToken).ConfigureAwait(false);
             return retryPath;
         }
@@ -253,7 +256,7 @@ namespace MediaBrowser.Providers.Manager
 
                 await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
                 {
-                    await source.CopyToAsync(fs, IODefaults.CopyToBufferSize, cancellationToken).ConfigureAwait(false);
+                    await source.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
                 }
 
                 if (_config.Configuration.SaveMetadataHidden)

+ 1 - 1
MediaBrowser.Providers/Subtitles/SubtitleManager.cs

@@ -148,7 +148,7 @@ namespace MediaBrowser.Providers.Subtitles
             CancellationToken cancellationToken)
         {
             var parts = subtitleId.Split(new[] { '_' }, 2);
-            var provider = GetProvider(parts.First());
+            var provider = GetProvider(parts[0]);
 
             var saveInMediaFolder = libraryOptions.SaveSubtitlesWithMedia;