aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--BDInfo/BDInfo.csproj1
-rw-r--r--DvdLib/DvdLib.csproj1
-rw-r--r--Emby.Dlna/Emby.Dlna.csproj1
-rw-r--r--Emby.Dlna/PlayTo/SsdpHttpClient.cs36
-rw-r--r--Emby.Drawing/Emby.Drawing.csproj7
-rw-r--r--Emby.Drawing/ImageProcessor.cs159
-rw-r--r--Emby.Drawing/NullImageEncoder.cs34
-rw-r--r--Emby.IsoMounting/IsoMounter/Configuration/PluginConfiguration.cs3
-rw-r--r--Emby.IsoMounting/IsoMounter/IsoMounter.csproj13
-rw-r--r--Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs477
-rw-r--r--Emby.IsoMounting/IsoMounter/LinuxMount.cs73
-rw-r--r--Emby.IsoMounting/IsoMounter/Plugin.cs25
-rw-r--r--Emby.Notifications/Emby.Notifications.csproj1
-rw-r--r--Emby.Notifications/NotificationManager.cs2
-rw-r--r--Emby.Photos/Emby.Photos.csproj1
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs50
-rw-r--r--Emby.Server.Implementations/Data/SqliteUserDataRepository.cs4
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs2
-rw-r--r--Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs4
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpListenerHost.cs107
-rw-r--r--Emby.Server.Implementations/IO/IsoManager.cs26
-rw-r--r--Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs44
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs358
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs184
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs128
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs85
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs78
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs19
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs21
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs171
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs8
-rw-r--r--Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs2
-rw-r--r--Emby.XmlTv/Emby.XmlTv/Emby.XmlTv.csproj1
-rw-r--r--Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj1
-rw-r--r--MediaBrowser.Api/Images/ImageService.cs8
-rw-r--r--MediaBrowser.Api/ItemUpdateService.cs4
-rw-r--r--MediaBrowser.Api/LocalizationService.cs5
-rw-r--r--MediaBrowser.Api/MediaBrowser.Api.csproj1
-rw-r--r--MediaBrowser.Api/UserService.cs11
-rw-r--r--MediaBrowser.Common/IApplicationHost.cs8
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj6
-rw-r--r--MediaBrowser.Controller/Authentication/AuthenticationException.cs1
-rw-r--r--MediaBrowser.Controller/Drawing/IImageEncoder.cs21
-rw-r--r--MediaBrowser.Controller/Drawing/IImageProcessor.cs30
-rw-r--r--MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs5
-rw-r--r--MediaBrowser.Controller/Entities/User.cs66
-rw-r--r--MediaBrowser.Controller/Library/IUserManager.cs10
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj1
-rw-r--r--MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj1
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj1
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs2
-rw-r--r--MediaBrowser.Model/Globalization/CultureDto.cs1
-rw-r--r--MediaBrowser.Model/Globalization/ILocalizationManager.cs35
-rw-r--r--MediaBrowser.Model/IO/IIsoManager.cs2
-rw-r--r--MediaBrowser.Model/IO/IIsoMounter.cs2
-rw-r--r--MediaBrowser.Model/IO/StreamDefaults.cs2
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj1
-rw-r--r--MediaBrowser.Model/Plugins/BasePluginConfiguration.cs2
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj1
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs32
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs170
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs17
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj1
-rw-r--r--MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj1
64 files changed, 1151 insertions, 1424 deletions
diff --git a/BDInfo/BDInfo.csproj b/BDInfo/BDInfo.csproj
index b2c752d0c..9dbaa9e2f 100644
--- a/BDInfo/BDInfo.csproj
+++ b/BDInfo/BDInfo.csproj
@@ -11,6 +11,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
diff --git a/DvdLib/DvdLib.csproj b/DvdLib/DvdLib.csproj
index b2c752d0c..9dbaa9e2f 100644
--- a/DvdLib/DvdLib.csproj
+++ b/DvdLib/DvdLib.csproj
@@ -11,6 +11,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj
index 4c07087c5..34b49120b 100644
--- a/Emby.Dlna/Emby.Dlna.csproj
+++ b/Emby.Dlna/Emby.Dlna.csproj
@@ -14,6 +14,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
index 217ea3a4b..66c634150 100644
--- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs
+++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
@@ -16,6 +16,8 @@ namespace Emby.Dlna.PlayTo
private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50";
private const string FriendlyName = "Jellyfin";
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
private readonly IHttpClient _httpClient;
private readonly IServerConfigurationManager _config;
@@ -25,7 +27,8 @@ namespace Emby.Dlna.PlayTo
_config = config;
}
- public async Task<XDocument> SendCommandAsync(string baseUrl,
+ public async Task<XDocument> SendCommandAsync(
+ string baseUrl,
DeviceService service,
string command,
string postData,
@@ -35,12 +38,20 @@ namespace Emby.Dlna.PlayTo
var cancellationToken = CancellationToken.None;
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
- using (var response = await PostSoapDataAsync(url, '\"' + service.ServiceType + '#' + command + '\"', postData, header, logRequest, cancellationToken)
+ using (var response = await PostSoapDataAsync(
+ url,
+ $"\"{service.ServiceType}#{command}\"",
+ postData,
+ header,
+ logRequest,
+ cancellationToken)
.ConfigureAwait(false))
using (var stream = response.Content)
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
- return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
+ return XDocument.Parse(
+ await reader.ReadToEndAsync().ConfigureAwait(false),
+ LoadOptions.PreserveWhitespace);
}
}
@@ -58,9 +69,8 @@ namespace Emby.Dlna.PlayTo
return baseUrl + serviceUrl;
}
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
- public async Task SubscribeAsync(string url,
+ public async Task SubscribeAsync(
+ string url,
string ip,
int port,
string localIp,
@@ -101,14 +111,12 @@ namespace Emby.Dlna.PlayTo
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
+ using (var stream = response.Content)
+ using (var reader = new StreamReader(stream, Encoding.UTF8))
{
- using (var stream = response.Content)
- {
- using (var reader = new StreamReader(stream, Encoding.UTF8))
- {
- return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
- }
- }
+ return XDocument.Parse(
+ await reader.ReadToEndAsync().ConfigureAwait(false),
+ LoadOptions.PreserveWhitespace);
}
}
@@ -122,7 +130,7 @@ namespace Emby.Dlna.PlayTo
{
if (soapAction[0] != '\"')
{
- soapAction = '\"' + soapAction + '\"';
+ soapAction = $"\"{soapAction}\"";
}
var options = new HttpRequestOptions
diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj
index 9f97baf77..2e539f2c7 100644
--- a/Emby.Drawing/Emby.Drawing.csproj
+++ b/Emby.Drawing/Emby.Drawing.csproj
@@ -3,6 +3,8 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
@@ -15,4 +17,9 @@
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
+ <PropertyGroup>
+ <!-- We need at least C# 7.1 for the "default literal" feature-->
+ <LangVersion>latest</LangVersion>
+ </PropertyGroup>
+
</Project>
diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs
index a7d95eb20..ce8089e59 100644
--- a/Emby.Drawing/ImageProcessor.cs
+++ b/Emby.Drawing/ImageProcessor.cs
@@ -22,42 +22,47 @@ using Microsoft.Extensions.Logging;
namespace Emby.Drawing
{
/// <summary>
- /// Class ImageProcessor
+ /// Class ImageProcessor.
/// </summary>
public class ImageProcessor : IImageProcessor, IDisposable
{
- /// <summary>
- /// The us culture
- /// </summary>
- protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
+ // Increment this when there's a change requiring caches to be invalidated
+ private const string Version = "3";
- /// <summary>
- /// Gets the list of currently registered image processors
- /// Image processors are specialized metadata providers that run after the normal ones
- /// </summary>
- /// <value>The image enhancers.</value>
- public IImageEnhancer[] ImageEnhancers { get; private set; }
+ private static readonly HashSet<string> _transparentImageTypes
+ = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
/// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
-
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths;
private IImageEncoder _imageEncoder;
private readonly Func<ILibraryManager> _libraryManager;
private readonly Func<IMediaEncoder> _mediaEncoder;
+ private readonly Dictionary<string, LockInfo> _locks = new Dictionary<string, LockInfo>();
+ private bool _disposed = false;
+
+ /// <summary>
+ ///
+ /// </summary>
+ /// <param name="logger"></param>
+ /// <param name="appPaths"></param>
+ /// <param name="fileSystem"></param>
+ /// <param name="imageEncoder"></param>
+ /// <param name="libraryManager"></param>
+ /// <param name="mediaEncoder"></param>
public ImageProcessor(
- ILoggerFactory loggerFactory,
+ ILogger<ImageProcessor> logger,
IServerApplicationPaths appPaths,
IFileSystem fileSystem,
IImageEncoder imageEncoder,
Func<ILibraryManager> libraryManager,
Func<IMediaEncoder> mediaEncoder)
{
- _logger = loggerFactory.CreateLogger(nameof(ImageProcessor));
+ _logger = logger;
_fileSystem = fileSystem;
_imageEncoder = imageEncoder;
_libraryManager = libraryManager;
@@ -69,20 +74,11 @@ namespace Emby.Drawing
ImageHelper.ImageProcessor = this;
}
- public IImageEncoder ImageEncoder
- {
- get => _imageEncoder;
- set
- {
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
+ private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images");
- _imageEncoder = value;
- }
- }
+ private string EnhancedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "enhanced-images");
+ /// <inheritdoc />
public IReadOnlyCollection<string> SupportedInputFormats =>
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
@@ -115,18 +111,20 @@ namespace Emby.Drawing
"wbmp"
};
+ /// <inheritdoc />
+ public IReadOnlyCollection<IImageEnhancer> ImageEnhancers { get; set; }
+ /// <inheritdoc />
public bool SupportsImageCollageCreation => _imageEncoder.SupportsImageCollageCreation;
- private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images");
-
- private string EnhancedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "enhanced-images");
-
- public void AddParts(IEnumerable<IImageEnhancer> enhancers)
+ /// <inheritdoc />
+ public IImageEncoder ImageEncoder
{
- ImageEnhancers = enhancers.ToArray();
+ get => _imageEncoder;
+ set => _imageEncoder = value ?? throw new ArgumentNullException(nameof(value));
}
+ /// <inheritdoc />
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
{
var file = await ProcessImage(options).ConfigureAwait(false);
@@ -137,15 +135,15 @@ namespace Emby.Drawing
}
}
+ /// <inheritdoc />
public IReadOnlyCollection<ImageFormat> GetSupportedImageOutputFormats()
=> _imageEncoder.SupportedOutputFormats;
- private static readonly HashSet<string> TransparentImageTypes
- = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
-
+ /// <inheritdoc />
public bool SupportsTransparency(string path)
- => TransparentImageTypes.Contains(Path.GetExtension(path));
+ => _transparentImageTypes.Contains(Path.GetExtension(path));
+ /// <inheritdoc />
public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
{
if (options == null)
@@ -187,9 +185,9 @@ namespace Emby.Drawing
}
dateModified = supportedImageInfo.dateModified;
- bool requiresTransparency = TransparentImageTypes.Contains(Path.GetExtension(originalImagePath));
+ bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath));
- if (options.Enhancers.Length > 0)
+ if (options.Enhancers.Count > 0)
{
if (item == null)
{
@@ -279,7 +277,7 @@ namespace Emby.Drawing
}
}
- private ImageFormat GetOutputFormat(ImageFormat[] clientSupportedFormats, bool requiresTransparency)
+ private ImageFormat GetOutputFormat(IReadOnlyCollection<ImageFormat> clientSupportedFormats, bool requiresTransparency)
{
var serverFormats = GetSupportedImageOutputFormats();
@@ -321,11 +319,6 @@ namespace Emby.Drawing
}
/// <summary>
- /// Increment this when there's a change requiring caches to be invalidated
- /// </summary>
- private const string Version = "3";
-
- /// <summary>
/// Gets the cache file path based on a set of parameters
/// </summary>
private string GetCacheFilePath(string originalPath, ImageDimensions outputSize, int quality, DateTime dateModified, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, int? blur, string backgroundColor, string foregroundLayer)
@@ -372,9 +365,11 @@ namespace Emby.Drawing
return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLowerInvariant());
}
+ /// <inheritdoc />
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info)
=> GetImageDimensions(item, info, true);
+ /// <inheritdoc />
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem)
{
int width = info.Width;
@@ -400,26 +395,19 @@ namespace Emby.Drawing
return size;
}
- /// <summary>
- /// Gets the size of the image.
- /// </summary>
+ /// <inheritdoc />
public ImageDimensions GetImageDimensions(string path)
=> _imageEncoder.GetImageSize(path);
- /// <summary>
- /// Gets the image cache tag.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="image">The image.</param>
- /// <returns>Guid.</returns>
- /// <exception cref="ArgumentNullException">item</exception>
+ /// <inheritdoc />
public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
{
- var supportedEnhancers = GetSupportedEnhancers(item, image.Type);
+ var supportedEnhancers = GetSupportedEnhancers(item, image.Type).ToArray();
return GetImageCacheTag(item, image, supportedEnhancers);
}
+ /// <inheritdoc />
public string GetImageCacheTag(BaseItem item, ChapterInfo chapter)
{
try
@@ -437,22 +425,15 @@ namespace Emby.Drawing
}
}
- /// <summary>
- /// Gets the image cache tag.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="image">The image.</param>
- /// <param name="imageEnhancers">The image enhancers.</param>
- /// <returns>Guid.</returns>
- /// <exception cref="ArgumentNullException">item</exception>
- public string GetImageCacheTag(BaseItem item, ItemImageInfo image, IImageEnhancer[] imageEnhancers)
+ /// <inheritdoc />
+ public string GetImageCacheTag(BaseItem item, ItemImageInfo image, IReadOnlyCollection<IImageEnhancer> imageEnhancers)
{
string originalImagePath = image.Path;
DateTime dateModified = image.DateModified;
ImageType imageType = image.Type;
// Optimization
- if (imageEnhancers.Length == 0)
+ if (imageEnhancers.Count == 0)
{
return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
@@ -480,7 +461,7 @@ namespace Emby.Drawing
{
try
{
- string filename = (originalImagePath + dateModified.Ticks.ToString(UsCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
+ string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
string cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png";
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
@@ -507,16 +488,10 @@ namespace Emby.Drawing
return (originalImagePath, dateModified);
}
- /// <summary>
- /// Gets the enhanced image.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="imageType">Type of the image.</param>
- /// <param name="imageIndex">Index of the image.</param>
- /// <returns>Task{System.String}.</returns>
+ /// <inheritdoc />
public async Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex)
{
- var enhancers = GetSupportedEnhancers(item, imageType);
+ var enhancers = GetSupportedEnhancers(item, imageType).ToArray();
ItemImageInfo imageInfo = item.GetImageInfo(imageType, imageIndex);
@@ -532,7 +507,7 @@ namespace Emby.Drawing
bool inputImageSupportsTransparency,
BaseItem item,
int imageIndex,
- IImageEnhancer[] enhancers,
+ IReadOnlyCollection<IImageEnhancer> enhancers,
CancellationToken cancellationToken)
{
var originalImagePath = image.Path;
@@ -573,6 +548,7 @@ namespace Emby.Drawing
/// <param name="imageIndex">Index of the image.</param>
/// <param name="supportedEnhancers">The supported enhancers.</param>
/// <param name="cacheGuid">The cache unique identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;System.String&gt;.</returns>
/// <exception cref="ArgumentNullException">
/// originalImagePath
@@ -584,9 +560,9 @@ namespace Emby.Drawing
BaseItem item,
ImageType imageType,
int imageIndex,
- IImageEnhancer[] supportedEnhancers,
+ IReadOnlyCollection<IImageEnhancer> supportedEnhancers,
string cacheGuid,
- CancellationToken cancellationToken)
+ CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(originalImagePath))
{
@@ -680,6 +656,7 @@ namespace Emby.Drawing
{
throw new ArgumentNullException(nameof(path));
}
+
if (string.IsNullOrEmpty(uniqueName))
{
throw new ArgumentNullException(nameof(uniqueName));
@@ -722,6 +699,7 @@ namespace Emby.Drawing
return Path.Combine(path, prefix, filename);
}
+ /// <inheritdoc />
public void CreateImageCollage(ImageCollageOptions options)
{
_logger.LogInformation("Creating image collage and saving to {Path}", options.OutputPath);
@@ -731,38 +709,25 @@ namespace Emby.Drawing
_logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath);
}
- public IImageEnhancer[] GetSupportedEnhancers(BaseItem item, ImageType imageType)
+ /// <inheritdoc />
+ public IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType)
{
- List<IImageEnhancer> list = null;
-
foreach (var i in ImageEnhancers)
{
- try
- {
- if (i.Supports(item, imageType))
- {
- if (list == null)
- {
- list = new List<IImageEnhancer>();
- }
- list.Add(i);
- }
- }
- catch (Exception ex)
+ if (i.Supports(item, imageType))
{
- _logger.LogError(ex, "Error in image enhancer: {0}", i.GetType().Name);
+ yield return i;
}
}
-
- return list == null ? Array.Empty<IImageEnhancer>() : list.ToArray();
}
- private Dictionary<string, LockInfo> _locks = new Dictionary<string, LockInfo>();
+
private class LockInfo
{
public SemaphoreSlim Lock = new SemaphoreSlim(1, 1);
public int Count = 1;
}
+
private LockInfo GetLock(string key)
{
lock (_locks)
@@ -795,7 +760,7 @@ namespace Emby.Drawing
}
}
- private bool _disposed;
+ /// <inheritdoc />
public void Dispose()
{
_disposed = true;
diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs
index fc4a5af9f..5af7f1622 100644
--- a/Emby.Drawing/NullImageEncoder.cs
+++ b/Emby.Drawing/NullImageEncoder.cs
@@ -5,36 +5,40 @@ using MediaBrowser.Model.Drawing;
namespace Emby.Drawing
{
+ /// <summary>
+ /// A fallback implementation of <see cref="IImageEncoder" />.
+ /// </summary>
public class NullImageEncoder : IImageEncoder
{
+ /// <inheritdoc />
public IReadOnlyCollection<string> SupportedInputFormats
=> new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "png", "jpeg", "jpg" };
+ /// <inheritdoc />
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
=> new HashSet<ImageFormat>() { ImageFormat.Jpg, ImageFormat.Png };
- public void CropWhiteSpace(string inputPath, string outputPath)
- {
- throw new NotImplementedException();
- }
-
- public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
- {
- throw new NotImplementedException();
- }
-
- public void CreateImageCollage(ImageCollageOptions options)
- {
- throw new NotImplementedException();
- }
-
+ /// <inheritdoc />
public string Name => "Null Image Encoder";
+ /// <inheritdoc />
public bool SupportsImageCollageCreation => false;
+ /// <inheritdoc />
public bool SupportsImageEncoding => false;
+ /// <inheritdoc />
public ImageDimensions GetImageSize(string path)
+ => throw new NotImplementedException();
+
+ /// <inheritdoc />
+ public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
+ {
+ throw new NotImplementedException();
+ }
+
+ /// <inheritdoc />
+ public void CreateImageCollage(ImageCollageOptions options)
{
throw new NotImplementedException();
}
diff --git a/Emby.IsoMounting/IsoMounter/Configuration/PluginConfiguration.cs b/Emby.IsoMounting/IsoMounter/Configuration/PluginConfiguration.cs
index 4755e4e82..ca6f40cc4 100644
--- a/Emby.IsoMounting/IsoMounter/Configuration/PluginConfiguration.cs
+++ b/Emby.IsoMounting/IsoMounter/Configuration/PluginConfiguration.cs
@@ -2,6 +2,9 @@ using MediaBrowser.Model.Plugins;
namespace IsoMounter.Configuration
{
+ /// <summary>
+ /// Class PluginConfiguration.
+ /// </summary>
public class PluginConfiguration : BasePluginConfiguration
{
}
diff --git a/Emby.IsoMounting/IsoMounter/IsoMounter.csproj b/Emby.IsoMounting/IsoMounter/IsoMounter.csproj
index dafa51cd5..4fa07fbf1 100644
--- a/Emby.IsoMounting/IsoMounter/IsoMounter.csproj
+++ b/Emby.IsoMounting/IsoMounter/IsoMounter.csproj
@@ -12,6 +12,19 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+
+ <!-- Code analysers-->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
+ </ItemGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <CodeAnalysisRuleSet>../../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>
diff --git a/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs b/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs
index 2f0003be8..48cb2e1d5 100644
--- a/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs
+++ b/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs
@@ -1,9 +1,10 @@
using System;
+using System.Diagnostics;
+using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
@@ -11,441 +12,274 @@ using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
namespace IsoMounter
{
+ /// <summary>
+ /// The ISO manager implementation for Linux.
+ /// </summary>
public class LinuxIsoManager : IIsoMounter
{
- [DllImport("libc", SetLastError = true)]
- static extern uint getuid();
+ private const string MountCommand = "mount";
+ private const string UnmountCommand = "umount";
+ private const string SudoCommand = "sudo";
- #region Private Fields
-
- private readonly bool ExecutablesAvailable;
private readonly ILogger _logger;
- private readonly string MountCommand;
- private readonly string MountPointRoot;
- private readonly IProcessFactory ProcessFactory;
- private readonly string SudoCommand;
- private readonly string UmountCommand;
-
- #endregion
+ private readonly string _mountPointRoot;
- #region Constructor(s)
-
- public LinuxIsoManager(ILogger logger, IProcessFactory processFactory)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="LinuxIsoManager" /> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ public LinuxIsoManager(ILogger logger)
{
_logger = logger;
- ProcessFactory = processFactory;
- MountPointRoot = Path.DirectorySeparatorChar + "tmp" + Path.DirectorySeparatorChar + "Emby";
+ _mountPointRoot = Path.DirectorySeparatorChar + "tmp" + Path.DirectorySeparatorChar + "Emby";
_logger.LogDebug(
"[{0}] System PATH is currently set to [{1}].",
Name,
- Environment.GetEnvironmentVariable("PATH") ?? ""
- );
+ Environment.GetEnvironmentVariable("PATH") ?? string.Empty);
_logger.LogDebug(
"[{0}] System path separator is [{1}].",
Name,
- Path.PathSeparator
- );
+ Path.PathSeparator);
_logger.LogDebug(
"[{0}] Mount point root is [{1}].",
Name,
- MountPointRoot
- );
-
- //
- // Get the location of the executables we need to support mounting/unmounting ISO images.
- //
-
- SudoCommand = GetFullPathForExecutable("sudo");
-
- _logger.LogInformation(
- "[{0}] Using version of [sudo] located at [{1}].",
- Name,
- SudoCommand
- );
-
- MountCommand = GetFullPathForExecutable("mount");
-
- _logger.LogInformation(
- "[{0}] Using version of [mount] located at [{1}].",
- Name,
- MountCommand
- );
-
- UmountCommand = GetFullPathForExecutable("umount");
-
- _logger.LogInformation(
- "[{0}] Using version of [umount] located at [{1}].",
- Name,
- UmountCommand
- );
-
- if (!string.IsNullOrEmpty(SudoCommand) && !string.IsNullOrEmpty(MountCommand) && !string.IsNullOrEmpty(UmountCommand))
- {
- ExecutablesAvailable = true;
- }
- else
- {
- ExecutablesAvailable = false;
- }
-
+ _mountPointRoot);
}
- #endregion
-
- #region Interface Implementation for IIsoMounter
-
- public bool IsInstalled => true;
-
+ /// <inheritdoc />
public string Name => "LinuxMount";
- public bool RequiresInstallation => false;
+#pragma warning disable SA1300
+#pragma warning disable SA1400
+ [DllImport("libc", SetLastError = true)]
+ static extern uint getuid();
+
+#pragma warning restore SA1300
+#pragma warning restore SA1400
+ /// <inheritdoc />
public bool CanMount(string path)
{
-
if (OperatingSystem.Id != OperatingSystemId.Linux)
{
return false;
}
+
_logger.LogInformation(
- "[{0}] Checking we can attempt to mount [{1}], Extension = [{2}], Operating System = [{3}], Executables Available = [{4}].",
+ "[{0}] Checking we can attempt to mount [{1}], Extension = [{2}], Operating System = [{3}].",
Name,
path,
Path.GetExtension(path),
- OperatingSystem.Name,
- ExecutablesAvailable
- );
+ OperatingSystem.Name);
- if (ExecutablesAvailable)
- {
- return string.Equals(Path.GetExtension(path), ".iso", StringComparison.OrdinalIgnoreCase);
- }
- else
- {
- return false;
- }
- }
-
- public Task Install(CancellationToken cancellationToken)
- {
- return Task.FromResult(false);
+ return string.Equals(Path.GetExtension(path), ".iso", StringComparison.OrdinalIgnoreCase);
}
+ /// <inheritdoc />
public Task<IIsoMount> Mount(string isoPath, CancellationToken cancellationToken)
{
- if (MountISO(isoPath, out LinuxMount mountedISO))
- {
- return Task.FromResult<IIsoMount>(mountedISO);
- }
- else
+ string cmdArguments;
+ string cmdFilename;
+ string mountPoint = Path.Combine(_mountPointRoot, Guid.NewGuid().ToString());
+
+ if (string.IsNullOrEmpty(isoPath))
{
- throw new IOException(string.Format(
- "An error occurred trying to mount image [$0].",
- isoPath
- ));
+ throw new ArgumentNullException(nameof(isoPath));
}
- }
- #endregion
-
- #region Interface Implementation for IDisposable
-
- // Flag: Has Dispose already been called?
- private bool disposed = false;
-
- public void Dispose()
- {
-
- // Dispose of unmanaged resources.
- Dispose(true);
-
- // Suppress finalization.
- GC.SuppressFinalize(this);
+ _logger.LogInformation(
+ "[{Name}] Attempting to mount [{Path}].",
+ Name,
+ isoPath);
- }
+ _logger.LogDebug(
+ "[{Name}] ISO will be mounted at [{Path}].",
+ Name,
+ mountPoint);
- protected virtual void Dispose(bool disposing)
- {
+ try
+ {
+ Directory.CreateDirectory(mountPoint);
+ }
+ catch (UnauthorizedAccessException ex)
+ {
+ throw new IOException("Unable to create mount point(Permission denied) for " + isoPath, ex);
+ }
+ catch (Exception ex)
+ {
+ throw new IOException("Unable to create mount point for " + isoPath, ex);
+ }
- if (disposed)
+ if (GetUID() == 0)
+ {
+ cmdFilename = MountCommand;
+ cmdArguments = string.Format(
+ CultureInfo.InvariantCulture,
+ "\"{0}\" \"{1}\"",
+ isoPath,
+ mountPoint);
+ }
+ else
{
- return;
+ cmdFilename = SudoCommand;
+ cmdArguments = string.Format(
+ CultureInfo.InvariantCulture,
+ "\"{0}\" \"{1}\" \"{2}\"",
+ MountCommand,
+ isoPath,
+ mountPoint);
}
- _logger.LogInformation(
- "[{0}] Disposing [{1}].",
+ _logger.LogDebug(
+ "[{0}] Mount command [{1}], mount arguments [{2}].",
Name,
- disposing
- );
+ cmdFilename,
+ cmdArguments);
- if (disposing)
+ int exitcode = ExecuteCommand(cmdFilename, cmdArguments);
+ if (exitcode == 0)
{
+ _logger.LogInformation(
+ "[{0}] ISO mount completed successfully.",
+ Name);
- //
- // Free managed objects here.
- //
-
+ return Task.FromResult<IIsoMount>(new LinuxMount(this, isoPath, mountPoint));
}
- //
- // Free any unmanaged objects here.
- //
-
- disposed = true;
-
- }
-
- #endregion
-
- #region Private Methods
-
- private string GetFullPathForExecutable(string name)
- {
+ _logger.LogInformation(
+ "[{0}] ISO mount completed with errors.",
+ Name);
- foreach (string test in (Environment.GetEnvironmentVariable("PATH") ?? "").Split(Path.PathSeparator))
+ try
{
- string path = test.Trim();
-
- if (!string.IsNullOrEmpty(path) && File.Exists(path = Path.Combine(path, name)))
- {
- return Path.GetFullPath(path);
- }
+ Directory.Delete(mountPoint, false);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "[{Name}] Unhandled exception removing mount point.", Name);
+ throw;
}
- return string.Empty;
+ throw new ExternalException("Mount command failed", exitcode);
}
private uint GetUID()
{
-
var uid = getuid();
_logger.LogDebug(
"[{0}] GetUserId() returned [{2}].",
Name,
- uid
- );
+ uid);
return uid;
-
}
- private bool ExecuteCommand(string cmdFilename, string cmdArguments)
+ private int ExecuteCommand(string cmdFilename, string cmdArguments)
{
-
- bool processFailed = false;
-
- var process = ProcessFactory.Create(
- new ProcessOptions
- {
- CreateNoWindow = true,
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- UseShellExecute = false,
- FileName = cmdFilename,
- Arguments = cmdArguments,
- IsHidden = true,
- ErrorDialog = false,
- EnableRaisingEvents = true
- }
- );
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = cmdFilename,
+ Arguments = cmdArguments,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ ErrorDialog = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true
+ };
+
+ var process = new Process()
+ {
+ StartInfo = startInfo
+ };
try
{
process.Start();
- //StreamReader outputReader = process.StandardOutput.;
- //StreamReader errorReader = process.StandardError;
-
_logger.LogDebug(
"[{Name}] Standard output from process is [{Error}].",
Name,
- process.StandardOutput.ReadToEnd()
- );
+ process.StandardOutput.ReadToEnd());
_logger.LogDebug(
"[{Name}] Standard error from process is [{Error}].",
Name,
- process.StandardError.ReadToEnd()
- );
+ process.StandardError.ReadToEnd());
+
+ return process.ExitCode;
}
catch (Exception ex)
{
- processFailed = true;
_logger.LogDebug(ex, "[{Name}] Unhandled exception executing command.", Name);
+ throw;
}
-
- if (!processFailed && process.ExitCode == 0)
+ finally
{
- return true;
+ process?.Dispose();
}
- else
- {
- return false;
- }
-
}
- private bool MountISO(string isoPath, out LinuxMount mountedISO)
+ /// <summary>
+ /// Unmounts the specified mount.
+ /// </summary>
+ /// <param name="mount">The mount.</param>
+ internal void OnUnmount(LinuxMount mount)
{
-
- string cmdArguments;
- string cmdFilename;
- string mountPoint = Path.Combine(MountPointRoot, Guid.NewGuid().ToString());
-
- if (!string.IsNullOrEmpty(isoPath))
- {
-
- _logger.LogInformation(
- "[{Name}] Attempting to mount [{Path}].",
- Name,
- isoPath
- );
-
- _logger.LogDebug(
- "[{Name}] ISO will be mounted at [{Path}].",
- Name,
- mountPoint
- );
-
- }
- else
+ if (mount == null)
{
-
- throw new ArgumentNullException(nameof(isoPath));
-
- }
-
- try
- {
- Directory.CreateDirectory(mountPoint);
- }
- catch (UnauthorizedAccessException)
- {
- throw new IOException("Unable to create mount point(Permission denied) for " + isoPath);
- }
- catch (Exception)
- {
- throw new IOException("Unable to create mount point for " + isoPath);
- }
-
- if (GetUID() == 0)
- {
- cmdFilename = MountCommand;
- cmdArguments = string.Format("\"{0}\" \"{1}\"", isoPath, mountPoint);
- }
- else
- {
- cmdFilename = SudoCommand;
- cmdArguments = string.Format("\"{0}\" \"{1}\" \"{2}\"", MountCommand, isoPath, mountPoint);
+ throw new ArgumentNullException(nameof(mount));
}
- _logger.LogDebug(
- "[{0}] Mount command [{1}], mount arguments [{2}].",
+ _logger.LogInformation(
+ "[{0}] Attempting to unmount ISO [{1}] mounted on [{2}].",
Name,
- cmdFilename,
- cmdArguments
- );
-
- if (ExecuteCommand(cmdFilename, cmdArguments))
- {
-
- _logger.LogInformation(
- "[{0}] ISO mount completed successfully.",
- Name
- );
-
- mountedISO = new LinuxMount(this, isoPath, mountPoint);
-
- }
- else
- {
-
- _logger.LogInformation(
- "[{0}] ISO mount completed with errors.",
- Name
- );
-
- try
- {
- Directory.Delete(mountPoint, false);
- }
- catch (Exception ex)
- {
- _logger.LogInformation(ex, "[{Name}] Unhandled exception removing mount point.", Name);
- }
-
- mountedISO = null;
-
- }
-
- return mountedISO != null;
-
- }
-
- private void UnmountISO(LinuxMount mount)
- {
+ mount.IsoPath,
+ mount.MountedPath);
string cmdArguments;
string cmdFilename;
- if (mount != null)
- {
-
- _logger.LogInformation(
- "[{0}] Attempting to unmount ISO [{1}] mounted on [{2}].",
- Name,
- mount.IsoPath,
- mount.MountedPath
- );
-
- }
- else
- {
-
- throw new ArgumentNullException(nameof(mount));
-
- }
-
if (GetUID() == 0)
{
- cmdFilename = UmountCommand;
- cmdArguments = string.Format("\"{0}\"", mount.MountedPath);
+ cmdFilename = UnmountCommand;
+ cmdArguments = string.Format(
+ CultureInfo.InvariantCulture,
+ "\"{0}\"",
+ mount.MountedPath);
}
else
{
cmdFilename = SudoCommand;
- cmdArguments = string.Format("\"{0}\" \"{1}\"", UmountCommand, mount.MountedPath);
+ cmdArguments = string.Format(
+ CultureInfo.InvariantCulture,
+ "\"{0}\" \"{1}\"",
+ UnmountCommand,
+ mount.MountedPath);
}
_logger.LogDebug(
"[{0}] Umount command [{1}], umount arguments [{2}].",
Name,
cmdFilename,
- cmdArguments
- );
+ cmdArguments);
- if (ExecuteCommand(cmdFilename, cmdArguments))
+ int exitcode = ExecuteCommand(cmdFilename, cmdArguments);
+ if (exitcode == 0)
{
-
_logger.LogInformation(
"[{0}] ISO unmount completed successfully.",
- Name
- );
-
+ Name);
}
else
{
-
_logger.LogInformation(
"[{0}] ISO unmount completed with errors.",
- Name
- );
-
+ Name);
}
try
@@ -454,24 +288,11 @@ namespace IsoMounter
}
catch (Exception ex)
{
- _logger.LogInformation(ex, "[{Name}] Unhandled exception removing mount point.", Name);
+ _logger.LogError(ex, "[{Name}] Unhandled exception removing mount point.", Name);
+ throw;
}
- }
-
- #endregion
-
- #region Internal Methods
-
- internal void OnUnmount(LinuxMount mount)
- {
-
- UnmountISO(mount);
+ throw new ExternalException("Mount command failed", exitcode);
}
-
- #endregion
-
}
-
}
-
diff --git a/Emby.IsoMounting/IsoMounter/LinuxMount.cs b/Emby.IsoMounting/IsoMounter/LinuxMount.cs
index b8636822b..ccad8ce20 100644
--- a/Emby.IsoMounting/IsoMounter/LinuxMount.cs
+++ b/Emby.IsoMounting/IsoMounter/LinuxMount.cs
@@ -3,81 +3,56 @@ using MediaBrowser.Model.IO;
namespace IsoMounter
{
+ /// <summary>
+ /// Class LinuxMount.
+ /// </summary>
internal class LinuxMount : IIsoMount
{
+ private readonly LinuxIsoManager _linuxIsoManager;
- #region Private Fields
-
- private readonly LinuxIsoManager linuxIsoManager;
-
- #endregion
-
- #region Constructor(s)
+ private bool _disposed = false;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="LinuxMount" /> class.
+ /// </summary>
+ /// <param name="isoManager">The ISO manager that mounted this ISO file.</param>
+ /// <param name="isoPath">The path to the ISO file.</param>
+ /// <param name="mountFolder">The folder the ISO is mounted in.</param>
internal LinuxMount(LinuxIsoManager isoManager, string isoPath, string mountFolder)
{
-
- linuxIsoManager = isoManager;
+ _linuxIsoManager = isoManager;
IsoPath = isoPath;
MountedPath = mountFolder;
-
}
- #endregion
-
- #region Interface Implementation for IDisposable
+ /// <inheritdoc />
+ public string IsoPath { get; }
- // Flag: Has Dispose already been called?
- private bool disposed = false;
+ /// <inheritdoc />
+ public string MountedPath { get; }
+ /// <inheritdoc />
public void Dispose()
{
-
- // Dispose of unmanaged resources.
Dispose(true);
-
- // Suppress finalization.
GC.SuppressFinalize(this);
-
}
+ /// <summary>
+ /// Releases the unmanaged resources and disposes of the managed resources used.
+ /// </summary>
+ /// <param name="disposing">Whether or not the managed resources should be disposed.</param>
protected virtual void Dispose(bool disposing)
{
-
- if (disposed)
+ if (_disposed)
{
return;
}
- if (disposing)
- {
-
- //
- // Free managed objects here.
- //
-
- linuxIsoManager.OnUnmount(this);
-
- }
-
- //
- // Free any unmanaged objects here.
- //
-
- disposed = true;
+ _linuxIsoManager.OnUnmount(this);
+ _disposed = true;
}
-
- #endregion
-
- #region Interface Implementation for IIsoMount
-
- public string IsoPath { get; private set; }
- public string MountedPath { get; private set; }
-
- #endregion
-
}
-
}
diff --git a/Emby.IsoMounting/IsoMounter/Plugin.cs b/Emby.IsoMounting/IsoMounter/Plugin.cs
index f45b39d3e..433294d74 100644
--- a/Emby.IsoMounting/IsoMounter/Plugin.cs
+++ b/Emby.IsoMounting/IsoMounter/Plugin.cs
@@ -6,25 +6,28 @@ using MediaBrowser.Model.Serialization;
namespace IsoMounter
{
+ /// <summary>
+ /// The LinuxMount plugin class.
+ /// </summary>
public class Plugin : BasePlugin<PluginConfiguration>
{
- public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Plugin" /> class.
+ /// </summary>
+ /// <param name="applicationPaths">The application paths.</param>
+ /// <param name="xmlSerializer">The XML serializer.</param>
+ public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
+ : base(applicationPaths, xmlSerializer)
{
}
- private Guid _id = new Guid("4682DD4C-A675-4F1B-8E7C-79ADF137A8F8");
- public override Guid Id => _id;
+ /// <inheritdoc />
+ public override Guid Id { get; } = new Guid("4682DD4C-A675-4F1B-8E7C-79ADF137A8F8");
- /// <summary>
- /// Gets the name of the plugin
- /// </summary>
- /// <value>The name.</value>
+ /// <inheritdoc />
public override string Name => "Iso Mounter";
- /// <summary>
- /// Gets the description.
- /// </summary>
- /// <value>The description.</value>
+ /// <inheritdoc />
public override string Description => "Mount and stream ISO contents";
}
}
diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj
index 5c68e48c8..cbd3bde4f 100644
--- a/Emby.Notifications/Emby.Notifications.csproj
+++ b/Emby.Notifications/Emby.Notifications.csproj
@@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
diff --git a/Emby.Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs
index a767e541e..eecbbea07 100644
--- a/Emby.Notifications/NotificationManager.cs
+++ b/Emby.Notifications/NotificationManager.cs
@@ -89,7 +89,7 @@ namespace Emby.Notifications
return _userManager.Users.Where(i => i.Policy.IsAdministrator)
.Select(i => i.Id);
case SendToUserType.All:
- return _userManager.Users.Select(i => i.Id);
+ return _userManager.UsersIds;
case SendToUserType.Custom:
return request.UserIds;
default:
diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj
index 8a79bf7e1..db73cb521 100644
--- a/Emby.Photos/Emby.Photos.csproj
+++ b/Emby.Photos/Emby.Photos.csproj
@@ -16,6 +16,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index e5e095ca1..6ab3d1bb1 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -315,8 +315,6 @@ namespace Emby.Server.Implementations
private IMediaSourceManager MediaSourceManager { get; set; }
- private IPlaylistManager PlaylistManager { get; set; }
-
private readonly IConfiguration _configuration;
/// <summary>
@@ -325,14 +323,6 @@ namespace Emby.Server.Implementations
/// <value>The installation manager.</value>
protected IInstallationManager InstallationManager { get; private set; }
- /// <summary>
- /// Gets or sets the zip client.
- /// </summary>
- /// <value>The zip client.</value>
- protected IZipClient ZipClient { get; private set; }
-
- protected IHttpResultFactory HttpResultFactory { get; private set; }
-
protected IAuthService AuthService { get; private set; }
public IStartupOptions StartupOptions { get; }
@@ -512,13 +502,8 @@ namespace Emby.Server.Implementations
return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i));
}
- /// <summary>
- /// Gets the exports.
- /// </summary>
- /// <typeparam name="T">The type</typeparam>
- /// <param name="manageLifetime">if set to <c>true</c> [manage lifetime].</param>
- /// <returns>IEnumerable{``0}.</returns>
- public IEnumerable<T> GetExports<T>(bool manageLifetime = true)
+ /// <inheritdoc />
+ public IReadOnlyCollection<T> GetExports<T>(bool manageLifetime = true)
{
var parts = GetExportTypes<T>()
.Select(CreateInstanceSafe)
@@ -540,6 +525,7 @@ namespace Emby.Server.Implementations
/// <summary>
/// Runs the startup tasks.
/// </summary>
+ /// <returns><see cref="Task" />.</returns>
public async Task RunStartupTasksAsync()
{
Logger.LogInformation("Running startup tasks");
@@ -552,7 +538,7 @@ namespace Emby.Server.Implementations
Logger.LogInformation("ServerId: {0}", SystemId);
- var entryPoints = GetExports<IServerEntryPoint>().ToList();
+ var entryPoints = GetExports<IServerEntryPoint>();
var stopWatch = new Stopwatch();
stopWatch.Start();
@@ -684,8 +670,6 @@ namespace Emby.Server.Implementations
await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
}
- public static IStreamHelper StreamHelper { get; set; }
-
/// <summary>
/// Registers resources that classes will depend on
/// </summary>
@@ -729,8 +713,7 @@ namespace Emby.Server.Implementations
ProcessFactory = new ProcessFactory();
serviceCollection.AddSingleton(ProcessFactory);
- ApplicationHost.StreamHelper = new StreamHelper();
- serviceCollection.AddSingleton(StreamHelper);
+ serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
serviceCollection.AddSingleton(typeof(ICryptoProvider), typeof(CryptographyProvider));
@@ -739,18 +722,16 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(typeof(IInstallationManager), typeof(InstallationManager));
- ZipClient = new ZipClient();
- serviceCollection.AddSingleton(ZipClient);
+ serviceCollection.AddSingleton(typeof(IZipClient), typeof(ZipClient));
- HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, StreamHelper);
- serviceCollection.AddSingleton(HttpResultFactory);
+ serviceCollection.AddSingleton(typeof(IHttpResultFactory), typeof(HttpResultFactory));
serviceCollection.AddSingleton<IServerApplicationHost>(this);
serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
serviceCollection.AddSingleton(ServerConfigurationManager);
- LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, LoggerFactory);
+ LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, LoggerFactory.CreateLogger<LocalizationManager>());
await LocalizationManager.LoadAll().ConfigureAwait(false);
serviceCollection.AddSingleton<ILocalizationManager>(LocalizationManager);
@@ -774,7 +755,8 @@ namespace Emby.Server.Implementations
_userRepository = GetUserRepository();
- UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, _userRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager);
+ UserManager = new UserManager(LoggerFactory.CreateLogger<UserManager>(), _userRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager);
+
serviceCollection.AddSingleton(UserManager);
LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager);
@@ -807,7 +789,7 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(HttpServer);
- ImageProcessor = GetImageProcessor();
+ ImageProcessor = new ImageProcessor(LoggerFactory.CreateLogger<ImageProcessor>(), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder);
serviceCollection.AddSingleton(ImageProcessor);
TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager);
@@ -840,8 +822,7 @@ namespace Emby.Server.Implementations
CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager);
serviceCollection.AddSingleton(CollectionManager);
- PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager);
- serviceCollection.AddSingleton(PlaylistManager);
+ serviceCollection.AddSingleton(typeof(IPlaylistManager), typeof(PlaylistManager));
LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager);
serviceCollection.AddSingleton(LiveTvManager);
@@ -959,11 +940,6 @@ namespace Emby.Server.Implementations
}
}
- private IImageProcessor GetImageProcessor()
- {
- return new ImageProcessor(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder);
- }
-
/// <summary>
/// Gets the user repository.
/// </summary>
@@ -1096,7 +1072,7 @@ namespace Emby.Server.Implementations
GetExports<IMetadataSaver>(),
GetExports<IExternalId>());
- ImageProcessor.AddParts(GetExports<IImageEnhancer>());
+ ImageProcessor.ImageEnhancers = GetExports<IImageEnhancer>();
LiveTvManager.AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
index 4035bb99d..9d4855bcf 100644
--- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
@@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.Data
var userDatasTableExists = TableExists(connection, "UserDatas");
var userDataTableExists = TableExists(connection, "userdata");
- var users = userDatasTableExists ? null : userManager.Users.ToArray();
+ var users = userDatasTableExists ? null : userManager.Users;
connection.RunInTransaction(db =>
{
@@ -84,7 +84,7 @@ namespace Emby.Server.Implementations.Data
}
}
- private void ImportUserIds(IDatabaseConnection db, User[] users)
+ private void ImportUserIds(IDatabaseConnection db, IEnumerable<User> users)
{
var userIdsWithUserData = GetAllUserIdsWithUserData(db);
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 6e7aa1313..1a7f10634 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -1364,7 +1364,7 @@ namespace Emby.Server.Implementations.Dto
return null;
}
- var supportedEnhancers = _imageProcessor.GetSupportedEnhancers(item, ImageType.Primary);
+ var supportedEnhancers = _imageProcessor.GetSupportedEnhancers(item, ImageType.Primary).ToArray();
ImageDimensions size;
diff --git a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs
index b7565adec..b2328121e 100644
--- a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs
+++ b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs
@@ -50,9 +50,7 @@ namespace Emby.Server.Implementations.EntryPoints
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
- var users = _userManager.Users.ToList();
-
- foreach (var user in users)
+ foreach (var user in _userManager.Users)
{
cancellationToken.ThrowIfCancellationRequested();
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
index 4c233456c..bdcf5d0b7 100644
--- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -7,6 +7,7 @@ using System.Net.Sockets;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
+using Emby.Server.Implementations.Configuration;
using Emby.Server.Implementations.Net;
using Emby.Server.Implementations.Services;
using MediaBrowser.Common.Extensions;
@@ -470,64 +471,10 @@ namespace Emby.Server.Implementations.HttpServer
urlToLog = GetUrlToLog(urlString);
- if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(localPath, "/" + _config.Configuration.BaseUrl + "/", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(localPath, "/" + _config.Configuration.BaseUrl, StringComparison.OrdinalIgnoreCase))
{
- httpRes.Redirect(_defaultRedirectPath);
- return;
- }
-
- if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
- {
- httpRes.Redirect("emby/" + _defaultRedirectPath);
- return;
- }
-
- if (localPath.IndexOf("mediabrowser/web", StringComparison.OrdinalIgnoreCase) != -1)
- {
- httpRes.StatusCode = 200;
- httpRes.ContentType = "text/html";
- var newUrl = urlString.Replace("mediabrowser", "emby", StringComparison.OrdinalIgnoreCase)
- .Replace("/dashboard/", "/web/", StringComparison.OrdinalIgnoreCase);
-
- if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase))
- {
- await httpRes.WriteAsync(
- "<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" +
- newUrl + "\">" + newUrl + "</a></body></html>",
- cancellationToken).ConfigureAwait(false);
- return;
- }
- }
-
- if (localPath.IndexOf("dashboard/", StringComparison.OrdinalIgnoreCase) != -1 &&
- localPath.IndexOf("web/dashboard", StringComparison.OrdinalIgnoreCase) == -1)
- {
- httpRes.StatusCode = 200;
- httpRes.ContentType = "text/html";
- var newUrl = urlString.Replace("mediabrowser", "emby", StringComparison.OrdinalIgnoreCase)
- .Replace("/dashboard/", "/web/", StringComparison.OrdinalIgnoreCase);
-
- if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase))
- {
- await httpRes.WriteAsync(
- "<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" +
- newUrl + "\">" + newUrl + "</a></body></html>",
- cancellationToken).ConfigureAwait(false);
- return;
- }
- }
-
- if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase))
- {
- httpRes.Redirect(_defaultRedirectPath);
- return;
- }
-
- if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase))
- {
- httpRes.Redirect("../" + _defaultRedirectPath);
+ httpRes.Redirect("/" + _config.Configuration.BaseUrl + "/" + _defaultRedirectPath);
return;
}
@@ -543,19 +490,6 @@ namespace Emby.Server.Implementations.HttpServer
return;
}
- if (!string.Equals(httpReq.QueryString["r"], "0", StringComparison.OrdinalIgnoreCase))
- {
- if (localPath.EndsWith("web/dashboard.html", StringComparison.OrdinalIgnoreCase))
- {
- httpRes.Redirect("index.html#!/dashboard.html");
- }
-
- if (localPath.EndsWith("web/home.html", StringComparison.OrdinalIgnoreCase))
- {
- httpRes.Redirect("index.html");
- }
- }
-
if (!string.IsNullOrEmpty(GlobalResponse))
{
// We don't want the address pings in ApplicationHost to fail
@@ -569,7 +503,6 @@ namespace Emby.Server.Implementations.HttpServer
}
var handler = GetServiceHandler(httpReq);
-
if (handler != null)
{
await handler.ProcessRequestAsync(this, httpReq, httpRes, _logger, cancellationToken).ConfigureAwait(false);
@@ -663,22 +596,14 @@ namespace Emby.Server.Implementations.HttpServer
foreach (var route in clone)
{
- routes.Add(new RouteAttribute(NormalizeEmbyRoutePath(route.Path), route.Verbs)
+ routes.Add(new RouteAttribute(NormalizeCustomRoutePath(_config.Configuration.BaseUrl, route.Path), route.Verbs)
{
Notes = route.Notes,
Priority = route.Priority,
Summary = route.Summary
});
- routes.Add(new RouteAttribute(NormalizeMediaBrowserRoutePath(route.Path), route.Verbs)
- {
- Notes = route.Notes,
- Priority = route.Priority,
- Summary = route.Summary
- });
-
- // needed because apps add /emby, and some users also add /emby, thereby double prefixing
- routes.Add(new RouteAttribute(DoubleNormalizeEmbyRoutePath(route.Path), route.Verbs)
+ routes.Add(new RouteAttribute(NormalizeOldRoutePath(route.Path), route.Verbs)
{
Notes = route.Notes,
Priority = route.Priority,
@@ -719,8 +644,8 @@ namespace Emby.Server.Implementations.HttpServer
return _socketListener.ProcessWebSocketRequest(context);
}
- //TODO Add Jellyfin Route Path Normalizer
- private static string NormalizeEmbyRoutePath(string path)
+ // this method was left for compatibility with third party clients
+ private static string NormalizeOldRoutePath(string path)
{
if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase))
{
@@ -730,24 +655,14 @@ namespace Emby.Server.Implementations.HttpServer
return "emby/" + path;
}
- private static string NormalizeMediaBrowserRoutePath(string path)
- {
- if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase))
- {
- return "/mediabrowser" + path;
- }
-
- return "mediabrowser/" + path;
- }
-
- private static string DoubleNormalizeEmbyRoutePath(string path)
+ private static string NormalizeCustomRoutePath(string baseUrl, string path)
{
if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase))
{
- return "/emby/emby" + path;
+ return "/" + baseUrl + path;
}
- return "emby/emby/" + path;
+ return baseUrl + "/" + path;
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/IO/IsoManager.cs b/Emby.Server.Implementations/IO/IsoManager.cs
index f0a15097c..94e92c2a6 100644
--- a/Emby.Server.Implementations/IO/IsoManager.cs
+++ b/Emby.Server.Implementations/IO/IsoManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -8,12 +9,12 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.IO
{
/// <summary>
- /// Class IsoManager
+ /// Class IsoManager.
/// </summary>
public class IsoManager : IIsoManager
{
/// <summary>
- /// The _mounters
+ /// The _mounters.
/// </summary>
private readonly List<IIsoMounter> _mounters = new List<IIsoMounter>();
@@ -22,9 +23,7 @@ namespace Emby.Server.Implementations.IO
/// </summary>
/// <param name="isoPath">The iso path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>IsoMount.</returns>
- /// <exception cref="ArgumentNullException">isoPath</exception>
- /// <exception cref="ArgumentException"></exception>
+ /// <returns><see creaf="IsoMount" />.</returns>
public Task<IIsoMount> Mount(string isoPath, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(isoPath))
@@ -36,7 +35,11 @@ namespace Emby.Server.Implementations.IO
if (mounter == null)
{
- throw new ArgumentException(string.Format("No mounters are able to mount {0}", isoPath));
+ throw new ArgumentException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "No mounters are able to mount {0}",
+ isoPath));
}
return mounter.Mount(isoPath, cancellationToken);
@@ -60,16 +63,5 @@ namespace Emby.Server.Implementations.IO
{
_mounters.AddRange(mounters);
}
-
- /// <summary>
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- /// </summary>
- public void Dispose()
- {
- foreach (var mounter in _mounters)
- {
- mounter.Dispose();
- }
- }
}
}
diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
index c7044820c..fa6bbcf91 100644
--- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
+++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
@@ -1,15 +1,12 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.IO;
-using System.Linq;
-using System.Text;
+using System.Security.Cryptography;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Users;
@@ -17,32 +14,37 @@ namespace Emby.Server.Implementations.Library
{
public class DefaultPasswordResetProvider : IPasswordResetProvider
{
- public string Name => "Default Password Reset Provider";
+ private const string BaseResetFileName = "passwordreset";
- public bool IsEnabled => true;
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly IUserManager _userManager;
private readonly string _passwordResetFileBase;
private readonly string _passwordResetFileBaseDir;
- private readonly string _passwordResetFileBaseName = "passwordreset";
- private readonly IJsonSerializer _jsonSerializer;
- private readonly IUserManager _userManager;
- private readonly ICryptoProvider _crypto;
-
- public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager, ICryptoProvider cryptoProvider)
+ public DefaultPasswordResetProvider(
+ IServerConfigurationManager configurationManager,
+ IJsonSerializer jsonSerializer,
+ IUserManager userManager)
{
_passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
- _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName);
+ _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName);
_jsonSerializer = jsonSerializer;
_userManager = userManager;
- _crypto = cryptoProvider;
}
+ /// <inheritdoc />
+ public string Name => "Default Password Reset Provider";
+
+ /// <inheritdoc />
+ public bool IsEnabled => true;
+
+ /// <inheritdoc />
public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
{
SerializablePasswordReset spr;
- HashSet<string> usersreset = new HashSet<string>();
- foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*"))
+ List<string> usersreset = new List<string>();
+ foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*"))
{
using (var str = File.OpenRead(resetfile))
{
@@ -53,12 +55,15 @@ namespace Emby.Server.Implementations.Library
{
File.Delete(resetfile);
}
- else if (spr.Pin.Replace("-", "").Equals(pin.Replace("-", ""), StringComparison.InvariantCultureIgnoreCase))
+ else if (string.Equals(
+ spr.Pin.Replace("-", string.Empty),
+ pin.Replace("-", string.Empty),
+ StringComparison.InvariantCultureIgnoreCase))
{
var resetUser = _userManager.GetUserByName(spr.UserName);
if (resetUser == null)
{
- throw new Exception($"User with a username of {spr.UserName} not found");
+ throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found");
}
await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
@@ -81,10 +86,11 @@ namespace Emby.Server.Implementations.Library
}
}
+ /// <inheritdoc />
public async Task<ForgotPasswordResult> StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
{
string pin = string.Empty;
- using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create())
+ using (var cryptoRandom = RandomNumberGenerator.Create())
{
byte[] bytes = new byte[4];
cryptoRandom.GetBytes(bytes);
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index c8c8a108d..086527883 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@@ -11,13 +12,11 @@ using MediaBrowser.Common.Events;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Providers;
@@ -41,34 +40,19 @@ namespace Emby.Server.Implementations.Library
public class UserManager : IUserManager
{
/// <summary>
- /// Gets the users.
- /// </summary>
- /// <value>The users.</value>
- public IEnumerable<User> Users => _users;
-
- private User[] _users;
-
- /// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
- /// <summary>
- /// Gets or sets the configuration manager.
- /// </summary>
- /// <value>The configuration manager.</value>
- private IServerConfigurationManager ConfigurationManager { get; set; }
+ private readonly object _policySyncLock = new object();
/// <summary>
/// Gets the active user repository
/// </summary>
/// <value>The user repository.</value>
- private IUserRepository UserRepository { get; set; }
- public event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
-
+ private readonly IUserRepository _userRepository;
private readonly IXmlSerializer _xmlSerializer;
private readonly IJsonSerializer _jsonSerializer;
-
private readonly INetworkManager _networkManager;
private readonly Func<IImageProcessor> _imageProcessorFactory;
@@ -76,6 +60,8 @@ namespace Emby.Server.Implementations.Library
private readonly IServerApplicationHost _appHost;
private readonly IFileSystem _fileSystem;
+ private ConcurrentDictionary<Guid, User> _users;
+
private IAuthenticationProvider[] _authenticationProviders;
private DefaultAuthenticationProvider _defaultAuthenticationProvider;
@@ -85,8 +71,7 @@ namespace Emby.Server.Implementations.Library
private DefaultPasswordResetProvider _defaultPasswordResetProvider;
public UserManager(
- ILoggerFactory loggerFactory,
- IServerConfigurationManager configurationManager,
+ ILogger<UserManager> logger,
IUserRepository userRepository,
IXmlSerializer xmlSerializer,
INetworkManager networkManager,
@@ -96,8 +81,8 @@ namespace Emby.Server.Implementations.Library
IJsonSerializer jsonSerializer,
IFileSystem fileSystem)
{
- _logger = loggerFactory.CreateLogger(nameof(UserManager));
- UserRepository = userRepository;
+ _logger = logger;
+ _userRepository = userRepository;
_xmlSerializer = xmlSerializer;
_networkManager = networkManager;
_imageProcessorFactory = imageProcessorFactory;
@@ -105,8 +90,51 @@ namespace Emby.Server.Implementations.Library
_appHost = appHost;
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
- ConfigurationManager = configurationManager;
- _users = Array.Empty<User>();
+ _users = null;
+ }
+
+ public event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
+
+ /// <summary>
+ /// Occurs when [user updated].
+ /// </summary>
+ public event EventHandler<GenericEventArgs<User>> UserUpdated;
+
+ public event EventHandler<GenericEventArgs<User>> UserPolicyUpdated;
+
+ public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
+
+ public event EventHandler<GenericEventArgs<User>> UserLockedOut;
+
+ public event EventHandler<GenericEventArgs<User>> UserCreated;
+
+ /// <summary>
+ /// Occurs when [user deleted].
+ /// </summary>
+ public event EventHandler<GenericEventArgs<User>> UserDeleted;
+
+ /// <inheritdoc />
+ public IEnumerable<User> Users => _users.Values;
+
+ /// <inheritdoc />
+ public IEnumerable<Guid> UsersIds => _users.Keys;
+
+ /// <summary>
+ /// Called when [user updated].
+ /// </summary>
+ /// <param name="user">The user.</param>
+ private void OnUserUpdated(User user)
+ {
+ UserUpdated?.Invoke(this, new GenericEventArgs<User> { Argument = user });
+ }
+
+ /// <summary>
+ /// Called when [user deleted].
+ /// </summary>
+ /// <param name="user">The user.</param>
+ private void OnUserDeleted(User user)
+ {
+ UserDeleted?.Invoke(this, new GenericEventArgs<User> { Argument = user });
}
public NameIdPair[] GetAuthenticationProviders()
@@ -137,7 +165,7 @@ namespace Emby.Server.Implementations.Library
.ToArray();
}
- public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders,IEnumerable<IPasswordResetProvider> passwordResetProviders)
+ public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders)
{
_authenticationProviders = authenticationProviders.ToArray();
@@ -150,54 +178,21 @@ namespace Emby.Server.Implementations.Library
_defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
}
- #region UserUpdated Event
/// <summary>
- /// Occurs when [user updated].
- /// </summary>
- public event EventHandler<GenericEventArgs<User>> UserUpdated;
- public event EventHandler<GenericEventArgs<User>> UserPolicyUpdated;
- public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
- public event EventHandler<GenericEventArgs<User>> UserLockedOut;
-
- /// <summary>
- /// Called when [user updated].
- /// </summary>
- /// <param name="user">The user.</param>
- private void OnUserUpdated(User user)
- {
- UserUpdated?.Invoke(this, new GenericEventArgs<User> { Argument = user });
- }
- #endregion
-
- #region UserDeleted Event
- /// <summary>
- /// Occurs when [user deleted].
- /// </summary>
- public event EventHandler<GenericEventArgs<User>> UserDeleted;
- /// <summary>
- /// Called when [user deleted].
- /// </summary>
- /// <param name="user">The user.</param>
- private void OnUserDeleted(User user)
- {
- UserDeleted?.Invoke(this, new GenericEventArgs<User> { Argument = user });
- }
- #endregion
-
- /// <summary>
- /// Gets a User by Id
+ /// Gets a User by Id.
/// </summary>
/// <param name="id">The id.</param>
/// <returns>User.</returns>
- /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentException"></exception>
public User GetUserById(Guid id)
{
if (id == Guid.Empty)
{
- throw new ArgumentException(nameof(id), "Guid can't be empty");
+ throw new ArgumentException("Guid can't be empty", nameof(id));
}
- return Users.FirstOrDefault(u => u.Id == id);
+ _users.TryGetValue(id, out User user);
+ return user;
}
/// <summary>
@@ -206,15 +201,13 @@ namespace Emby.Server.Implementations.Library
/// <param name="id">The identifier.</param>
/// <returns>User.</returns>
public User GetUserById(string id)
- {
- return GetUserById(new Guid(id));
- }
+ => GetUserById(new Guid(id));
public User GetUserByName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
- throw new ArgumentNullException(nameof(name));
+ throw new ArgumentException("Invalid username", nameof(name));
}
return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase));
@@ -222,8 +215,9 @@ namespace Emby.Server.Implementations.Library
public void Initialize()
{
- var users = LoadUsers();
- _users = users.ToArray();
+ LoadUsers();
+
+ var users = Users;
// If there are no local users with admin rights, make them all admins
if (!users.Any(i => i.Policy.IsAdministrator))
@@ -240,14 +234,12 @@ namespace Emby.Server.Implementations.Library
{
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
- // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.)
+ // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.)
return Regex.IsMatch(username, @"^[\w\-'._@]*$");
}
private static bool IsValidUsernameCharacter(char i)
- {
- return IsValidUsername(i.ToString());
- }
+ => IsValidUsername(i.ToString(CultureInfo.InvariantCulture));
public string MakeValidUsername(string username)
{
@@ -277,8 +269,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(username));
}
- var user = Users
- .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
+ var user = Users.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
var success = false;
string updatedUsername = null;
@@ -299,13 +290,12 @@ namespace Emby.Server.Implementations.Library
updatedUsername = authResult.username;
success = authResult.success;
- if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider))
+ if (success
+ && authenticationProvider != null
+ && !(authenticationProvider is DefaultAuthenticationProvider))
{
// We should trust the user that the authprovider says, not what was typed
- if (updatedUsername != username)
- {
- username = updatedUsername;
- }
+ username = updatedUsername;
// Search the database for the user again; the authprovider might have created it
user = Users
@@ -337,10 +327,11 @@ namespace Emby.Server.Implementations.Library
if (user.Policy.IsDisabled)
{
- throw new AuthenticationException(string.Format(
- CultureInfo.InvariantCulture,
- "The {0} account is currently disabled. Please consult with your administrator.",
- user.Name));
+ throw new AuthenticationException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "The {0} account is currently disabled. Please consult with your administrator.",
+ user.Name));
}
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
@@ -386,7 +377,7 @@ namespace Emby.Server.Implementations.Library
private IAuthenticationProvider GetAuthenticationProvider(User user)
{
- return GetAuthenticationProviders(user).First();
+ return GetAuthenticationProviders(user)[0];
}
private IPasswordResetProvider GetPasswordResetProvider(User user)
@@ -396,7 +387,7 @@ namespace Emby.Server.Implementations.Library
private IAuthenticationProvider[] GetAuthenticationProviders(User user)
{
- var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId;
+ var authenticationProviderId = user?.Policy.AuthenticationProviderId;
var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray();
@@ -438,16 +429,10 @@ namespace Emby.Server.Implementations.Library
{
try
{
- var requiresResolvedUser = provider as IRequiresResolvedUser;
- ProviderAuthenticationResult authenticationResult = null;
- if (requiresResolvedUser != null)
- {
- authenticationResult = await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false);
- }
- else
- {
- authenticationResult = await provider.Authenticate(username, password).ConfigureAwait(false);
- }
+
+ var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser
+ ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false)
+ : await provider.Authenticate(username, password).ConfigureAwait(false);
if (authenticationResult.Username != username)
{
@@ -467,7 +452,6 @@ namespace Emby.Server.Implementations.Library
private async Task<(IAuthenticationProvider authenticationProvider, string username, bool success)> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint)
{
- string updatedUsername = null;
bool success = false;
IAuthenticationProvider authenticationProvider = null;
@@ -487,7 +471,7 @@ namespace Emby.Server.Implementations.Library
foreach (var provider in GetAuthenticationProviders(user))
{
var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
- updatedUsername = providerAuthResult.username;
+ var updatedUsername = providerAuthResult.username;
success = providerAuthResult.success;
if (success)
@@ -499,25 +483,32 @@ namespace Emby.Server.Implementations.Library
}
}
- if (user != null)
+ if (user != null
+ && !success
+ && _networkManager.IsInLocalNetwork(remoteEndPoint)
+ && user.Configuration.EnableLocalPassword)
{
- if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword)
+ if (password == null)
{
- if (password == null)
- {
- // legacy
- success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
- }
- else
- {
- success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase);
- }
+ // legacy
+ success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
+ }
+ else
+ {
+ success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase);
}
}
return (authenticationProvider, username, success);
}
+ private string GetLocalPasswordHash(User user)
+ {
+ return string.IsNullOrEmpty(user.EasyPassword)
+ ? null
+ : PasswordHash.ConvertToByteString(new PasswordHash(user.EasyPassword).Hash);
+ }
+
private void UpdateInvalidLoginAttemptCount(User user, int newValue)
{
if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0)
@@ -556,17 +547,17 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Loads the users from the repository
+ /// Loads the users from the repository.
/// </summary>
- /// <returns>IEnumerable{User}.</returns>
- private List<User> LoadUsers()
+ private void LoadUsers()
{
- var users = UserRepository.RetrieveAllUsers();
+ var users = _userRepository.RetrieveAllUsers();
// There always has to be at least one user.
if (users.Count != 0)
{
- return users;
+ _users = new ConcurrentDictionary<Guid, User>(
+ users.Select(x => new KeyValuePair<Guid, User>(x.Id, x)));
}
var defaultName = Environment.UserName;
@@ -581,14 +572,15 @@ namespace Emby.Server.Implementations.Library
user.DateLastSaved = DateTime.UtcNow;
- UserRepository.CreateUser(user);
+ _userRepository.CreateUser(user);
user.Policy.IsAdministrator = true;
user.Policy.EnableContentDeletion = true;
user.Policy.EnableRemoteControlOfOtherUsers = true;
UpdateUserPolicy(user, user.Policy, false);
- return new List<User> { user };
+ _users = new ConcurrentDictionary<Guid, User>();
+ _users[user.Id] = user;
}
public UserDto GetUserDto(User user, string remoteEndPoint = null)
@@ -619,7 +611,7 @@ namespace Emby.Server.Implementations.Library
Policy = user.Policy
};
- if (!hasPassword && Users.Count() == 1)
+ if (!hasPassword && _users.Count == 1)
{
dto.EnableAutoLogin = true;
}
@@ -694,22 +686,26 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(user));
}
- if (string.IsNullOrEmpty(newName))
+ if (string.IsNullOrWhiteSpace(newName))
{
- throw new ArgumentNullException(nameof(newName));
+ throw new ArgumentException("Invalid username", nameof(newName));
}
- if (Users.Any(u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase)))
+ if (user.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))
{
- throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", newName));
+ throw new ArgumentException("The new and old names must be different.");
}
- if (user.Name.Equals(newName, StringComparison.Ordinal))
+ if (Users.Any(
+ u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase)))
{
- throw new ArgumentException("The new and old names must be different.");
+ throw new ArgumentException(string.Format(
+ CultureInfo.InvariantCulture,
+ "A user with the name '{0}' already exists.",
+ newName));
}
- await user.Rename(newName);
+ await user.Rename(newName).ConfigureAwait(false);
OnUserUpdated(user);
}
@@ -727,23 +723,30 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(user));
}
- if (user.Id.Equals(Guid.Empty) || !Users.Any(u => u.Id.Equals(user.Id)))
+ if (user.Id == Guid.Empty)
{
- throw new ArgumentException(string.Format("User with name '{0}' and Id {1} does not exist.", user.Name, user.Id));
+ throw new ArgumentException("Id can't be empty.", nameof(user));
+ }
+
+ if (!_users.ContainsKey(user.Id))
+ {
+ throw new ArgumentException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "A user '{0}' with Id {1} does not exist.",
+ user.Name,
+ user.Id),
+ nameof(user));
}
user.DateModified = DateTime.UtcNow;
user.DateLastSaved = DateTime.UtcNow;
- UserRepository.UpdateUser(user);
+ _userRepository.UpdateUser(user);
OnUserUpdated(user);
}
- public event EventHandler<GenericEventArgs<User>> UserCreated;
-
- private readonly SemaphoreSlim _userListLock = new SemaphoreSlim(1, 1);
-
/// <summary>
/// Creates the user.
/// </summary>
@@ -751,7 +754,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>User.</returns>
/// <exception cref="ArgumentNullException">name</exception>
/// <exception cref="ArgumentException"></exception>
- public async Task<User> CreateUser(string name)
+ public User CreateUser(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
@@ -768,28 +771,17 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", name));
}
- await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
-
- try
- {
- var user = InstantiateNewUser(name);
+ var user = InstantiateNewUser(name);
- var list = Users.ToList();
- list.Add(user);
- _users = list.ToArray();
+ _users[user.Id] = user;
- user.DateLastSaved = DateTime.UtcNow;
+ user.DateLastSaved = DateTime.UtcNow;
- UserRepository.CreateUser(user);
+ _userRepository.CreateUser(user);
- EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs<User> { Argument = user }, _logger);
+ EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs<User> { Argument = user }, _logger);
- return user;
- }
- finally
- {
- _userListLock.Release();
- }
+ return user;
}
/// <summary>
@@ -799,57 +791,59 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task.</returns>
/// <exception cref="ArgumentNullException">user</exception>
/// <exception cref="ArgumentException"></exception>
- public async Task DeleteUser(User user)
+ public void DeleteUser(User user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
- var allUsers = Users.ToList();
-
- if (allUsers.FirstOrDefault(u => u.Id == user.Id) == null)
+ if (!_users.ContainsKey(user.Id))
{
- throw new ArgumentException(string.Format("The user cannot be deleted because there is no user with the Name {0} and Id {1}.", user.Name, user.Id));
+ throw new ArgumentException(string.Format(
+ CultureInfo.InvariantCulture,
+ "The user cannot be deleted because there is no user with the Name {0} and Id {1}.",
+ user.Name,
+ user.Id));
}
- if (allUsers.Count == 1)
+ if (_users.Count == 1)
{
- throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one user in the system.", user.Name));
+ throw new ArgumentException(string.Format(
+ CultureInfo.InvariantCulture,
+ "The user '{0}' cannot be deleted because there must be at least one user in the system.",
+ user.Name));
}
- if (user.Policy.IsAdministrator && allUsers.Count(i => i.Policy.IsAdministrator) == 1)
+ if (user.Policy.IsAdministrator
+ && Users.Count(i => i.Policy.IsAdministrator) == 1)
{
- throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one admin user in the system.", user.Name));
+ throw new ArgumentException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "The user '{0}' cannot be deleted because there must be at least one admin user in the system.",
+ user.Name),
+ nameof(user));
}
- await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
+ var configPath = GetConfigurationFilePath(user);
+
+ _userRepository.DeleteUser(user);
try
{
- var configPath = GetConfigurationFilePath(user);
-
- UserRepository.DeleteUser(user);
-
- try
- {
- _fileSystem.DeleteFile(configPath);
- }
- catch (IOException ex)
- {
- _logger.LogError(ex, "Error deleting file {path}", configPath);
- }
-
- DeleteUserPolicy(user);
-
- _users = allUsers.Where(i => i.Id != user.Id).ToArray();
-
- OnUserDeleted(user);
+ _fileSystem.DeleteFile(configPath);
}
- finally
+ catch (IOException ex)
{
- _userListLock.Release();
+ _logger.LogError(ex, "Error deleting file {path}", configPath);
}
+
+ DeleteUserPolicy(user);
+
+ _users.TryRemove(user.Id, out _);
+
+ OnUserDeleted(user);
}
/// <summary>
@@ -906,8 +900,7 @@ namespace Emby.Server.Implementations.Library
Name = name,
Id = Guid.NewGuid(),
DateCreated = DateTime.UtcNow,
- DateModified = DateTime.UtcNow,
- UsesIdForConfigurationPath = true
+ DateModified = DateTime.UtcNow
};
}
@@ -989,7 +982,6 @@ namespace Emby.Server.Implementations.Library
};
}
- private readonly object _policySyncLock = new object();
public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy)
{
var user = GetUserById(userId);
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index ed254accb..85754ca8b 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -4,6 +4,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
@@ -31,6 +32,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly IServerApplicationHost _appHost;
private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager;
+ private readonly IStreamHelper _streamHelper;
public HdHomerunHost(
IServerConfigurationManager config,
@@ -40,29 +42,25 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
IHttpClient httpClient,
IServerApplicationHost appHost,
ISocketFactory socketFactory,
- INetworkManager networkManager)
+ INetworkManager networkManager,
+ IStreamHelper streamHelper)
: base(config, logger, jsonSerializer, fileSystem)
{
_httpClient = httpClient;
_appHost = appHost;
_socketFactory = socketFactory;
_networkManager = networkManager;
+ _streamHelper = streamHelper;
}
public string Name => "HD Homerun";
- public override string Type => DeviceType;
-
- public static string DeviceType => "hdhomerun";
+ public override string Type => "hdhomerun";
protected override string ChannelIdPrefix => "hdhr_";
private string GetChannelId(TunerHostInfo info, Channels i)
- {
- var id = ChannelIdPrefix + i.GuideNumber;
-
- return id;
- }
+ => ChannelIdPrefix + i.GuideNumber;
private async Task<List<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
{
@@ -74,19 +72,18 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
CancellationToken = cancellationToken,
BufferContent = false
};
- using (var response = await _httpClient.SendAsync(options, "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))
+ using (var stream = response.Content)
+ {
+ var lineup = await JsonSerializer.DeserializeFromStreamAsync<List<Channels>>(stream).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();
}
}
@@ -139,23 +136,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Url = string.Format("{0}/discover.json", GetApiUrl(info)),
CancellationToken = cancellationToken,
BufferContent = false
-
- }, "GET").ConfigureAwait(false))
+ }, HttpMethod.Get).ConfigureAwait(false))
+ using (var stream = response.Content)
{
- using (var stream = response.Content)
- {
- var discoverResponse = await JsonSerializer.DeserializeFromStreamAsync<DiscoverResponse>(stream).ConfigureAwait(false);
+ var discoverResponse = await JsonSerializer.DeserializeFromStreamAsync<DiscoverResponse>(stream).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)
@@ -186,36 +180,36 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
- using (var stream = await _httpClient.Get(new HttpRequestOptions()
+ using (var response = await _httpClient.SendAsync(new HttpRequestOptions()
{
Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
CancellationToken = cancellationToken,
BufferContent = false
- }))
+ }, HttpMethod.Get))
+ using (var stream = response.Content)
+ using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
{
var tuners = new List<LiveTvTunerInfo>();
- using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
+ while (!sr.EndOfStream)
{
- while (!sr.EndOfStream)
+ string line = StripXML(sr.ReadLine());
+ if (line.Contains("Channel"))
{
- string line = StripXML(sr.ReadLine());
- if (line.Contains("Channel"))
+ LiveTvTunerStatus status;
+ var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
+ var name = line.Substring(0, index - 1);
+ var currentChannel = line.Substring(index + 7);
+ if (currentChannel != "none") { status = LiveTvTunerStatus.LiveTv; } else { status = LiveTvTunerStatus.Available; }
+ tuners.Add(new LiveTvTunerInfo
{
- LiveTvTunerStatus status;
- var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
- var name = line.Substring(0, index - 1);
- var currentChannel = line.Substring(index + 7);
- if (currentChannel != "none") { status = LiveTvTunerStatus.LiveTv; } else { status = LiveTvTunerStatus.Available; }
- tuners.Add(new LiveTvTunerInfo
- {
- Name = name,
- SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
- ProgramName = currentChannel,
- Status = status
- });
- }
+ Name = name,
+ SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
+ ProgramName = currentChannel,
+ Status = status
+ });
}
}
+
return tuners;
}
}
@@ -245,6 +239,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
bufferIndex++;
}
}
+
return new string(buffer, 0, bufferIndex);
}
@@ -256,7 +251,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var uri = new Uri(GetApiUrl(info));
- using (var manager = new HdHomerunManager(Logger))
+ using (var manager = new HdHomerunManager())
{
// Legacy HdHomeruns are IPv4 only
var ipInfo = IPAddress.Parse(uri.Host);
@@ -276,6 +271,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
});
}
}
+
return tuners;
}
@@ -434,12 +430,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
videoCodec = channelInfo.VideoCodec;
}
+
string audioCodec = channelInfo.AudioCodec;
if (!videoBitrate.HasValue)
{
videoBitrate = isHd ? 15000000 : 2000000;
}
+
int? audioBitrate = isHd ? 448000 : 192000;
// normalize
@@ -461,6 +459,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
id = "native";
}
+
id += "_" + channelId.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_" + url.GetMD5().ToString("N", CultureInfo.InvariantCulture);
var mediaSource = new MediaSourceInfo
@@ -527,29 +526,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
else
{
- try
- {
- var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
+ var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
- if (modelInfo != null && modelInfo.SupportsTranscoding)
+ if (modelInfo != null && modelInfo.SupportsTranscoding)
+ {
+ if (info.AllowHWTranscoding)
{
- if (info.AllowHWTranscoding)
- {
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy"));
-
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet540"));
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet480"));
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet360"));
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240"));
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile"));
- }
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy"));
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet540"));
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet480"));
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet360"));
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240"));
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile"));
}
- }
- catch
- {
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
}
if (list.Count == 0)
@@ -582,7 +574,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
{
- return new HdHomerunUdpStream(mediaSource, info, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
+ return new HdHomerunUdpStream(
+ mediaSource,
+ info,
+ streamId,
+ new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path),
+ modelInfo.TunerCount,
+ FileSystem,
+ Logger,
+ Config.ApplicationPaths,
+ _appHost,
+ _socketFactory,
+ _networkManager,
+ _streamHelper);
}
var enableHttpStream = true;
@@ -599,10 +603,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
mediaSource.Path = httpUrl;
- return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
- }
-
- return new HdHomerunUdpStream(mediaSource, info, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
+ return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _streamHelper);
+ }
+
+ return new HdHomerunUdpStream(
+ mediaSource,
+ info,
+ streamId,
+ new HdHomerunChannelCommands(hdhomerunChannel.Number, profile),
+ modelInfo.TunerCount,
+ FileSystem,
+ Logger,
+ Config.ApplicationPaths,
+ _appHost,
+ _socketFactory,
+ _networkManager,
+ _streamHelper);
}
public async Task Validate(TunerHostInfo info)
@@ -701,9 +717,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
catch (OperationCanceledException)
{
}
- catch
+ catch (Exception ex)
{
// Socket timeout indicates all messages have been received.
+ Logger.LogError(ex, "Error while sending discovery message");
}
}
@@ -718,21 +735,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Url = url
};
- try
- {
- var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false);
-
- hostInfo.DeviceId = modelInfo.DeviceID;
- hostInfo.FriendlyName = modelInfo.FriendlyName;
+ var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false);
- return hostInfo;
- }
- catch
- {
- // logged at lower levels
- }
+ hostInfo.DeviceId = modelInfo.DeviceID;
+ hostInfo.FriendlyName = modelInfo.FriendlyName;
- return null;
+ return hostInfo;
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index c19552428..3699b988c 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -1,6 +1,7 @@
using System;
using System.Buffers;
using System.Collections.Generic;
+using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Text;
@@ -8,13 +9,12 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.LiveTv;
-using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
public interface IHdHomerunChannelCommands
{
- IEnumerable<Tuple<string, string>> GetCommands();
+ IEnumerable<(string, string)> GetCommands();
}
public class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands
@@ -33,16 +33,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
- public IEnumerable<Tuple<string, string>> GetCommands()
+ public IEnumerable<(string, string)> GetCommands()
{
- var commands = new List<Tuple<string, string>>();
-
if (!string.IsNullOrEmpty(_channel))
- commands.Add(Tuple.Create("channel", _channel));
+ {
+ yield return ("channel", _channel);
+ }
if (!string.IsNullOrEmpty(_program))
- commands.Add(Tuple.Create("program", _program));
- return commands;
+ {
+ yield return ("program", _program);
+ }
}
}
@@ -57,29 +58,27 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_profile = profile;
}
- public IEnumerable<Tuple<string, string>> GetCommands()
+ public IEnumerable<(string, string)> GetCommands()
{
- var commands = new List<Tuple<string, string>>();
-
if (!string.IsNullOrEmpty(_channel))
{
- if (!string.IsNullOrEmpty(_profile) && !string.Equals(_profile, "native", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrEmpty(_profile)
+ && !string.Equals(_profile, "native", StringComparison.OrdinalIgnoreCase))
{
- commands.Add(Tuple.Create("vchannel", string.Format("{0} transcode={1}", _channel, _profile)));
+ yield return ("vchannel", $"{_channel} transcode={_profile}");
}
else
{
- commands.Add(Tuple.Create("vchannel", _channel));
+ yield return ("vchannel", _channel);
}
}
-
- return commands;
}
}
public class HdHomerunManager : IDisposable
{
public const int HdHomeRunPort = 65001;
+
// Message constants
private const byte GetSetName = 3;
private const byte GetSetValue = 4;
@@ -87,19 +86,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private const ushort GetSetRequest = 4;
private const ushort GetSetReply = 5;
- private readonly ILogger _logger;
-
private uint? _lockkey = null;
private int _activeTuner = -1;
private IPEndPoint _remoteEndPoint;
private TcpClient _tcpClient;
- public HdHomerunManager(ILogger logger)
- {
- _logger = logger;
- }
-
public void Dispose()
{
using (var socket = _tcpClient)
@@ -108,8 +100,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
_tcpClient = null;
- var task = StopStreaming(socket);
- Task.WaitAll(task);
+ StopStreaming(socket).GetAwaiter().GetResult();
}
}
}
@@ -173,20 +164,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null);
await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+
// parse response to make sure it worked
- if (!ParseReturnMessage(buffer, receivedBytes, out var returnVal))
+ if (!ParseReturnMessage(buffer, receivedBytes, out _))
{
continue;
}
var commandList = commands.GetCommands();
- foreach (Tuple<string, string> command in commandList)
+ foreach (var command in commandList)
{
var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue);
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+
// parse response to make sure it worked
- if (!ParseReturnMessage(buffer, receivedBytes, out returnVal))
+ if (!ParseReturnMessage(buffer, receivedBytes, out _))
{
await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
continue;
@@ -198,8 +191,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false);
receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+
// parse response to make sure it worked
- if (!ParseReturnMessage(buffer, receivedBytes, out returnVal))
+ if (!ParseReturnMessage(buffer, receivedBytes, out _))
{
await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
continue;
@@ -231,13 +225,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
try
{
- foreach (Tuple<string, string> command in commandList)
+ foreach (var command in commandList)
{
var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey);
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+
// parse response to make sure it worked
- if (!ParseReturnMessage(buffer, receivedBytes, out string returnVal))
+ if (!ParseReturnMessage(buffer, receivedBytes, out _))
{
return;
}
@@ -264,21 +259,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private async Task ReleaseLockkey(TcpClient client, uint lockKeyValue)
{
- _logger.LogInformation("HdHomerunManager.ReleaseLockkey {0}", lockKeyValue);
-
var stream = client.GetStream();
var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue);
- await stream.WriteAsync(releaseTarget, 0, releaseTarget.Length, CancellationToken.None).ConfigureAwait(false);
+ await stream.WriteAsync(releaseTarget, 0, releaseTarget.Length).ConfigureAwait(false);
var buffer = ArrayPool<byte>.Shared.Rent(8192);
try
{
- await stream.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None).ConfigureAwait(false);
+ await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue);
_lockkey = null;
- await stream.WriteAsync(releaseKeyMsg, 0, releaseKeyMsg.Length, CancellationToken.None).ConfigureAwait(false);
- await stream.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None).ConfigureAwait(false);
+ await stream.WriteAsync(releaseKeyMsg, 0, releaseKeyMsg.Length).ConfigureAwait(false);
+ await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
}
finally
{
@@ -288,7 +281,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private static byte[] CreateGetMessage(int tuner, string name)
{
- var byteName = Encoding.UTF8.GetBytes(string.Format("/tuner{0}/{1}\0", tuner, name));
+ var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}\0", tuner, name));
int messageLength = byteName.Length + 10; // 4 bytes for header + 4 bytes for crc + 2 bytes for tag name and length
var message = new byte[messageLength];
@@ -311,12 +304,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private static byte[] CreateSetMessage(int tuner, string name, string value, uint? lockkey)
{
- var byteName = Encoding.UTF8.GetBytes(string.Format("/tuner{0}/{1}\0", tuner, name));
- var byteValue = Encoding.UTF8.GetBytes(string.Format("{0}\0", value));
+ var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}\0", tuner, name));
+ var byteValue = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}\0", value));
int messageLength = byteName.Length + byteValue.Length + 12;
if (lockkey.HasValue)
+ {
messageLength += 6;
+ }
var message = new byte[messageLength];
@@ -324,21 +319,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
bool flipEndian = BitConverter.IsLittleEndian;
- message[offset] = GetSetValue;
- offset++;
- message[offset] = Convert.ToByte(byteValue.Length);
- offset++;
+ message[offset++] = GetSetValue;
+ message[offset++] = Convert.ToByte(byteValue.Length);
Buffer.BlockCopy(byteValue, 0, message, offset, byteValue.Length);
offset += byteValue.Length;
if (lockkey.HasValue)
{
- message[offset] = GetSetLockkey;
- offset++;
- message[offset] = (byte)4;
- offset++;
+ message[offset++] = GetSetLockkey;
+ message[offset++] = 4;
var lockKeyBytes = BitConverter.GetBytes(lockkey.Value);
if (flipEndian)
+ {
Array.Reverse(lockKeyBytes);
+ }
+
Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4);
offset += 4;
}
@@ -346,7 +340,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
// calculate crc and insert at the end of the message
var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
if (flipEndian)
+ {
Array.Reverse(crcBytes);
+ }
+
Buffer.BlockCopy(crcBytes, 0, message, offset, 4);
return message;
@@ -375,10 +372,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
offset += 2;
// insert tag name and length
- message[offset] = GetSetName;
- offset++;
- message[offset] = Convert.ToByte(byteName.Length);
- offset++;
+ message[offset++] = GetSetName;
+ message[offset++] = Convert.ToByte(byteName.Length);
// insert name string
Buffer.BlockCopy(byteName, 0, message, offset, byteName.Length);
@@ -392,7 +387,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
returnVal = string.Empty;
if (numBytes < 4)
+ {
return false;
+ }
var flipEndian = BitConverter.IsLittleEndian;
int offset = 0;
@@ -400,45 +397,49 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Buffer.BlockCopy(buf, offset, msgTypeBytes, 0, msgTypeBytes.Length);
if (flipEndian)
+ {
Array.Reverse(msgTypeBytes);
+ }
var msgType = BitConverter.ToUInt16(msgTypeBytes, 0);
offset += 2;
if (msgType != GetSetReply)
+ {
return false;
+ }
byte[] msgLengthBytes = new byte[2];
Buffer.BlockCopy(buf, offset, msgLengthBytes, 0, msgLengthBytes.Length);
if (flipEndian)
+ {
Array.Reverse(msgLengthBytes);
+ }
var msgLength = BitConverter.ToUInt16(msgLengthBytes, 0);
offset += 2;
if (numBytes < msgLength + 8)
+ {
return false;
+ }
- var nameTag = buf[offset];
- offset++;
+ var nameTag = buf[offset++];
- var nameLength = buf[offset];
- offset++;
+ var nameLength = buf[offset++];
// skip the name field to get to value for return
offset += nameLength;
- var valueTag = buf[offset];
- offset++;
+ var valueTag = buf[offset++];
- var valueLength = buf[offset];
- offset++;
+ var valueLength = buf[offset++];
returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator
return true;
}
- private class HdHomerunCrc
+ private static class HdHomerunCrc
{
private static uint[] crc_table = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
@@ -510,15 +511,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
var hash = 0xffffffff;
for (var i = 0; i < numBytes; i++)
+ {
hash = (hash >> 8) ^ crc_table[(hash ^ bytes[i]) & 0xff];
+ }
var tmp = ~hash & 0xffffffff;
var b0 = tmp & 0xff;
var b1 = (tmp >> 8) & 0xff;
var b2 = (tmp >> 16) & 0xff;
var b3 = (tmp >> 24) & 0xff;
- hash = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
- return hash;
+ return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index 1d79a5f96..fbbab07f8 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -1,4 +1,5 @@
using System;
+using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Net;
@@ -18,6 +19,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
public class HdHomerunUdpStream : LiveStream, IDirectStreamProvider
{
+ private const int RtpHeaderBytes = 12;
+
private readonly IServerApplicationHost _appHost;
private readonly MediaBrowser.Model.Net.ISocketFactory _socketFactory;
@@ -32,13 +35,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
IHdHomerunChannelCommands channelCommands,
int numTuners,
IFileSystem fileSystem,
- IHttpClient httpClient,
ILogger logger,
IServerApplicationPaths appPaths,
IServerApplicationHost appHost,
MediaBrowser.Model.Net.ISocketFactory socketFactory,
- INetworkManager networkManager)
- : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths)
+ INetworkManager networkManager,
+ IStreamHelper streamHelper)
+ : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
{
_appHost = appHost;
_socketFactory = socketFactory;
@@ -80,12 +83,18 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
var udpClient = _socketFactory.CreateUdpSocket(localPort);
- var hdHomerunManager = new HdHomerunManager(Logger);
+ var hdHomerunManager = new HdHomerunManager();
try
{
// send url to start streaming
- await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelCommands, _numTuners, openCancellationToken).ConfigureAwait(false);
+ await hdHomerunManager.StartStreaming(
+ remoteAddress,
+ localAddress,
+ localPort,
+ _channelCommands,
+ _numTuners,
+ openCancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -103,7 +112,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var taskCompletionSource = new TaskCompletionSource<bool>();
- await StartStreaming(udpClient, hdHomerunManager, remoteAddress, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
+ await StartStreaming(
+ udpClient,
+ hdHomerunManager,
+ remoteAddress,
+ taskCompletionSource,
+ LiveStreamCancellationTokenSource.Token).ConfigureAwait(false);
//OpenedMediaSource.Protocol = MediaProtocol.File;
//OpenedMediaSource.Path = tempFile;
@@ -148,50 +162,43 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
});
}
- private static void Resolve(TaskCompletionSource<bool> openTaskCompletionSource)
- {
- Task.Run(() =>
- {
- openTaskCompletionSource.TrySetResult(true);
- });
- }
-
- private const int RtpHeaderBytes = 12;
-
private async Task CopyTo(MediaBrowser.Model.Net.ISocket udpClient, string file, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
{
- var bufferSize = 81920;
-
- byte[] buffer = new byte[bufferSize];
- int read;
- var resolved = false;
-
- using (var source = _socketFactory.CreateNetworkStream(udpClient, false))
- using (var fileStream = FileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
+ byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamDefaults.DefaultCopyToBufferSize);
+ try
{
- var currentCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token).Token;
-
- while ((read = await source.ReadAsync(buffer, 0, buffer.Length, currentCancellationToken).ConfigureAwait(false)) != 0)
+ using (var source = _socketFactory.CreateNetworkStream(udpClient, false))
+ using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read))
{
- cancellationToken.ThrowIfCancellationRequested();
+ var currentCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token).Token;
+ int read;
+ var resolved = false;
+ while ((read = await source.ReadAsync(buffer, 0, buffer.Length, currentCancellationToken).ConfigureAwait(false)) != 0)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
- currentCancellationToken = cancellationToken;
+ currentCancellationToken = cancellationToken;
- read -= RtpHeaderBytes;
+ read -= RtpHeaderBytes;
- if (read > 0)
- {
- fileStream.Write(buffer, RtpHeaderBytes, read);
- }
+ if (read > 0)
+ {
+ await fileStream.WriteAsync(buffer, RtpHeaderBytes, read).ConfigureAwait(false);
+ }
- if (!resolved)
- {
- resolved = true;
- DateOpened = DateTime.UtcNow;
- Resolve(openTaskCompletionSource);
+ if (!resolved)
+ {
+ resolved = true;
+ DateOpened = DateTime.UtcNow;
+ openTaskCompletionSource.TrySetResult(true);
+ }
}
}
}
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(buffer);
+ }
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
index b4395e2e1..d12c96392 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
@@ -16,27 +16,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
public class LiveStream : ILiveStream
{
- public MediaSourceInfo OriginalMediaSource { get; set; }
- public MediaSourceInfo MediaSource { get; set; }
-
- public int ConsumerCount { get; set; }
-
- public string OriginalStreamId { get; set; }
- public bool EnableStreamSharing { get; set; }
- public string UniqueId { get; }
-
protected readonly IFileSystem FileSystem;
protected readonly IServerApplicationPaths AppPaths;
+ protected readonly IStreamHelper StreamHelper;
protected string TempFilePath;
protected readonly ILogger Logger;
protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource();
- public string TunerHostId { get; }
-
- public DateTime DateOpened { get; protected set; }
-
- public LiveStream(MediaSourceInfo mediaSource, TunerHostInfo tuner, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths)
+ public LiveStream(
+ MediaSourceInfo mediaSource,
+ TunerHostInfo tuner,
+ IFileSystem fileSystem,
+ ILogger logger,
+ IServerApplicationPaths appPaths,
+ IStreamHelper streamHelper)
{
OriginalMediaSource = mediaSource;
FileSystem = fileSystem;
@@ -51,11 +45,27 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
AppPaths = appPaths;
+ StreamHelper = streamHelper;
ConsumerCount = 1;
SetTempFilePath("ts");
}
+ protected virtual int EmptyReadLimit => 1000;
+
+ public MediaSourceInfo OriginalMediaSource { get; set; }
+ public MediaSourceInfo MediaSource { get; set; }
+
+ public int ConsumerCount { get; set; }
+
+ public string OriginalStreamId { get; set; }
+ public bool EnableStreamSharing { get; set; }
+ public string UniqueId { get; }
+
+ public string TunerHostId { get; }
+
+ public DateTime DateOpened { get; protected set; }
+
protected void SetTempFilePath(string extension)
{
TempFilePath = Path.Combine(AppPaths.GetTranscodingTempPath(), UniqueId + "." + extension);
@@ -71,24 +81,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
EnableStreamSharing = false;
- Logger.LogInformation("Closing " + GetType().Name);
+ Logger.LogInformation("Closing {Type}", GetType().Name);
LiveStreamCancellationTokenSource.Cancel();
return Task.CompletedTask;
}
- protected Stream GetInputStream(string path, bool allowAsyncFileRead)
- {
- var fileOpenOptions = FileOpenOptions.SequentialScan;
-
- if (allowAsyncFileRead)
- {
- fileOpenOptions |= FileOpenOptions.Asynchronous;
- }
-
- return FileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, fileOpenOptions);
- }
+ protected FileStream GetInputStream(string path, bool allowAsyncFileRead)
+ => new FileStream(
+ path,
+ FileMode.Open,
+ FileAccess.Read,
+ FileShare.ReadWrite,
+ StreamDefaults.DefaultFileStreamBufferSize,
+ allowAsyncFileRead ? FileOptions.SequentialScan | FileOptions.Asynchronous : FileOptions.SequentialScan);
public Task DeleteTempFiles()
{
@@ -144,8 +151,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
var nextFileInfo = GetNextFile(null);
- var nextFile = nextFileInfo.Item1;
- var isLastFile = nextFileInfo.Item2;
+ var nextFile = nextFileInfo.file;
+ var isLastFile = nextFileInfo.isLastFile;
while (!string.IsNullOrEmpty(nextFile))
{
@@ -155,8 +162,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
seekFile = false;
nextFileInfo = GetNextFile(nextFile);
- nextFile = nextFileInfo.Item1;
- isLastFile = nextFileInfo.Item2;
+ nextFile = nextFileInfo.file;
+ isLastFile = nextFileInfo.isLastFile;
}
Logger.LogInformation("Live Stream ended.");
@@ -180,19 +187,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private async Task CopyFile(string path, bool seekFile, int emptyReadLimit, bool allowAsync, Stream stream, CancellationToken cancellationToken)
{
- using (var inputStream = (FileStream)GetInputStream(path, allowAsync))
+ using (var inputStream = GetInputStream(path, allowAsync))
{
if (seekFile)
{
TrySeek(inputStream, -20000);
}
- await ApplicationHost.StreamHelper.CopyToAsync(inputStream, stream, 81920, emptyReadLimit, cancellationToken).ConfigureAwait(false);
+ await StreamHelper.CopyToAsync(
+ inputStream,
+ stream,
+ StreamDefaults.DefaultCopyToBufferSize,
+ emptyReadLimit,
+ cancellationToken).ConfigureAwait(false);
}
}
- protected virtual int EmptyReadLimit => 1000;
-
private void TrySeek(FileStream stream, long offset)
{
if (!stream.CanSeek)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 6c5c80827..a02a9ade4 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -28,14 +28,25 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private readonly IServerApplicationHost _appHost;
private readonly INetworkManager _networkManager;
private readonly IMediaSourceManager _mediaSourceManager;
-
- public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, INetworkManager networkManager)
+ private readonly IStreamHelper _streamHelper;
+
+ public M3UTunerHost(
+ IServerConfigurationManager config,
+ IMediaSourceManager mediaSourceManager,
+ ILogger logger,
+ IJsonSerializer jsonSerializer,
+ IFileSystem fileSystem,
+ IHttpClient httpClient,
+ IServerApplicationHost appHost,
+ INetworkManager networkManager,
+ IStreamHelper streamHelper)
: base(config, logger, jsonSerializer, fileSystem)
{
_httpClient = httpClient;
_appHost = appHost;
_networkManager = networkManager;
_mediaSourceManager = mediaSourceManager;
+ _streamHelper = streamHelper;
}
public override string Type => "m3u";
@@ -103,11 +114,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
- return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
+ return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _streamHelper);
}
}
- return new LiveStream(mediaSource, info, FileSystem, Logger, Config.ApplicationPaths);
+ return new LiveStream(mediaSource, info, FileSystem, Logger, Config.ApplicationPaths, _streamHelper);
}
public async Task Validate(TunerHostInfo info)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index 7de9931c7..c6e894560 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -19,8 +19,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private readonly IHttpClient _httpClient;
private readonly IServerApplicationHost _appHost;
- public SharedHttpStream(MediaSourceInfo mediaSource, TunerHostInfo tunerHostInfo, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost)
- : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths)
+ public SharedHttpStream(
+ MediaSourceInfo mediaSource,
+ TunerHostInfo tunerHostInfo,
+ string originalStreamId,
+ IFileSystem fileSystem,
+ IHttpClient httpClient,
+ ILogger logger,
+ IServerApplicationPaths appPaths,
+ IServerApplicationHost appHost,
+ IStreamHelper streamHelper)
+ : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
{
_httpClient = httpClient;
_appHost = appHost;
@@ -118,7 +127,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
using (var stream = response.Content)
using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
{
- await ApplicationHost.StreamHelper.CopyToAsync(stream, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken).ConfigureAwait(false);
+ await StreamHelper.CopyToAsync(
+ stream,
+ fileStream,
+ StreamDefaults.DefaultCopyToBufferSize,
+ () => Resolve(openTaskCompletionSource),
+ cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
@@ -128,6 +142,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
Logger.LogError(ex, "Error copying live stream.");
}
+
EnableStreamSharing = false;
await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
});
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index 8c49b6405..13cdc50ca 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -17,43 +17,49 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Localization
{
/// <summary>
- /// Class LocalizationManager
+ /// Class LocalizationManager.
/// </summary>
public class LocalizationManager : ILocalizationManager
{
- /// <summary>
- /// The _configuration manager
- /// </summary>
- private readonly IServerConfigurationManager _configurationManager;
+ private const string DefaultCulture = "en-US";
+ private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly;
+ private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
/// <summary>
- /// The us culture
+ /// The _configuration manager.
/// </summary>
- private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+ private readonly IServerConfigurationManager _configurationManager;
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly ILogger _logger;
private readonly Dictionary<string, Dictionary<string, ParentalRating>> _allParentalRatings =
new Dictionary<string, Dictionary<string, ParentalRating>>(StringComparer.OrdinalIgnoreCase);
- private readonly IJsonSerializer _jsonSerializer;
- private readonly ILogger _logger;
- private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly;
+ private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries =
+ new ConcurrentDictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
+
+ private List<CultureDto> _cultures;
/// <summary>
/// Initializes a new instance of the <see cref="LocalizationManager" /> class.
/// </summary>
/// <param name="configurationManager">The configuration manager.</param>
/// <param name="jsonSerializer">The json serializer.</param>
- /// <param name="loggerFactory">The logger factory</param>
+ /// <param name="logger">The logger.</param>
public LocalizationManager(
IServerConfigurationManager configurationManager,
IJsonSerializer jsonSerializer,
- ILoggerFactory loggerFactory)
+ ILogger<LocalizationManager> logger)
{
_configurationManager = configurationManager;
_jsonSerializer = jsonSerializer;
- _logger = loggerFactory.CreateLogger(nameof(LocalizationManager));
+ _logger = logger;
}
+ /// <summary>
+ /// Loads all resources into memory.
+ /// </summary>
+ /// <returns><see cref="Task" />.</returns>
public async Task LoadAll()
{
const string RatingsResource = "Emby.Server.Implementations.Localization.Ratings.";
@@ -82,9 +88,10 @@ namespace Emby.Server.Implementations.Localization
string[] parts = line.Split(',');
if (parts.Length == 2
- && int.TryParse(parts[1], NumberStyles.Integer, UsCulture, out var value))
+ && int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{
- dict.Add(parts[0], new ParentalRating { Name = parts[0], Value = value });
+ var name = parts[0];
+ dict.Add(name, new ParentalRating(name, value));
}
#if DEBUG
else
@@ -101,16 +108,11 @@ namespace Emby.Server.Implementations.Localization
await LoadCultures().ConfigureAwait(false);
}
- public string NormalizeFormKD(string text)
- => text.Normalize(NormalizationForm.FormKD);
-
- private CultureDto[] _cultures;
-
/// <summary>
/// Gets the cultures.
/// </summary>
- /// <returns>IEnumerable{CultureDto}.</returns>
- public CultureDto[] GetCultures()
+ /// <returns><see cref="IEnumerable{CultureDto}" />.</returns>
+ public IEnumerable<CultureDto> GetCultures()
=> _cultures;
private async Task LoadCultures()
@@ -168,9 +170,10 @@ namespace Emby.Server.Implementations.Localization
}
}
- _cultures = list.ToArray();
+ _cultures = list;
}
+ /// <inheritdoc />
public CultureDto FindLanguageInfo(string language)
=> GetCultures()
.FirstOrDefault(i =>
@@ -179,25 +182,19 @@ namespace Emby.Server.Implementations.Localization
|| i.ThreeLetterISOLanguageNames.Contains(language, StringComparer.OrdinalIgnoreCase)
|| string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
- /// <summary>
- /// Gets the countries.
- /// </summary>
- /// <returns>IEnumerable{CountryInfo}.</returns>
- public Task<CountryInfo[]> GetCountries()
- => _jsonSerializer.DeserializeFromStreamAsync<CountryInfo[]>(
+ /// <inheritdoc />
+ public IEnumerable<CountryInfo> GetCountries()
+ => _jsonSerializer.DeserializeFromStream<IEnumerable<CountryInfo>>(
_assembly.GetManifestResourceStream("Emby.Server.Implementations.Localization.countries.json"));
- /// <summary>
- /// Gets the parental ratings.
- /// </summary>
- /// <returns>IEnumerable{ParentalRating}.</returns>
+ /// <inheritdoc />
public IEnumerable<ParentalRating> GetParentalRatings()
=> GetParentalRatingsDictionary().Values;
/// <summary>
/// Gets the parental ratings dictionary.
/// </summary>
- /// <returns>Dictionary{System.StringParentalRating}.</returns>
+ /// <returns><see cref="Dictionary{String, ParentalRating}" />.</returns>
private Dictionary<string, ParentalRating> GetParentalRatingsDictionary()
{
var countryCode = _configurationManager.Configuration.MetadataCountryCode;
@@ -207,14 +204,14 @@ namespace Emby.Server.Implementations.Localization
countryCode = "us";
}
- return GetRatings(countryCode) ?? GetRatings("us");
+ return GetRatings(countryCode) ?? GetRatings("us");
}
/// <summary>
/// Gets the ratings.
/// </summary>
/// <param name="countryCode">The country code.</param>
- /// <returns>The ratings</returns>
+ /// <returns>The ratings.</returns>
private Dictionary<string, ParentalRating> GetRatings(string countryCode)
{
_allParentalRatings.TryGetValue(countryCode, out var value);
@@ -222,14 +219,7 @@ namespace Emby.Server.Implementations.Localization
return value;
}
- private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
-
/// <inheritdoc />
- /// <summary>
- /// Gets the rating level.
- /// </summary>
- /// <param name="rating">Rating field</param>
- /// <returns>The rating level</returns>&gt;
public int? GetRatingLevel(string rating)
{
if (string.IsNullOrEmpty(rating))
@@ -277,6 +267,7 @@ namespace Emby.Server.Implementations.Localization
return null;
}
+ /// <inheritdoc />
public bool HasUnicodeCategory(string value, UnicodeCategory category)
{
foreach (var chr in value)
@@ -290,11 +281,13 @@ namespace Emby.Server.Implementations.Localization
return false;
}
+ /// <inheritdoc />
public string GetLocalizedString(string phrase)
{
return GetLocalizedString(phrase, _configurationManager.Configuration.UICulture);
}
+ /// <inheritdoc />
public string GetLocalizedString(string phrase, string culture)
{
if (string.IsNullOrEmpty(culture))
@@ -317,12 +310,7 @@ namespace Emby.Server.Implementations.Localization
return phrase;
}
- private const string DefaultCulture = "en-US";
-
- private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries =
- new ConcurrentDictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
-
- public Dictionary<string, string> GetLocalizationDictionary(string culture)
+ private Dictionary<string, string> GetLocalizationDictionary(string culture)
{
if (string.IsNullOrEmpty(culture))
{
@@ -332,8 +320,9 @@ namespace Emby.Server.Implementations.Localization
const string prefix = "Core";
var key = prefix + culture;
- return _dictionaries.GetOrAdd(key,
- f => GetDictionary(prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult());
+ return _dictionaries.GetOrAdd(
+ key,
+ f => GetDictionary(prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult());
}
private async Task<Dictionary<string, string>> GetDictionary(string prefix, string culture, string baseFilename)
@@ -390,45 +379,45 @@ namespace Emby.Server.Implementations.Localization
return culture + ".json";
}
- public LocalizationOption[] GetLocalizationOptions()
- => new LocalizationOption[]
- {
- new LocalizationOption("Arabic", "ar"),
- new LocalizationOption("Bulgarian (Bulgaria)", "bg-BG"),
- new LocalizationOption("Catalan", "ca"),
- new LocalizationOption("Chinese Simplified", "zh-CN"),
- new LocalizationOption("Chinese Traditional", "zh-TW"),
- new LocalizationOption("Croatian", "hr"),
- new LocalizationOption("Czech", "cs"),
- new LocalizationOption("Danish", "da"),
- new LocalizationOption("Dutch", "nl"),
- new LocalizationOption("English (United Kingdom)", "en-GB"),
- new LocalizationOption("English (United States)", "en-US"),
- new LocalizationOption("French", "fr"),
- new LocalizationOption("French (Canada)", "fr-CA"),
- new LocalizationOption("German", "de"),
- new LocalizationOption("Greek", "el"),
- new LocalizationOption("Hebrew", "he"),
- new LocalizationOption("Hungarian", "hu"),
- new LocalizationOption("Italian", "it"),
- new LocalizationOption("Kazakh", "kk"),
- new LocalizationOption("Korean", "ko"),
- new LocalizationOption("Lithuanian", "lt-LT"),
- new LocalizationOption("Malay", "ms"),
- new LocalizationOption("Norwegian Bokmål", "nb"),
- new LocalizationOption("Persian", "fa"),
- new LocalizationOption("Polish", "pl"),
- new LocalizationOption("Portuguese (Brazil)", "pt-BR"),
- new LocalizationOption("Portuguese (Portugal)", "pt-PT"),
- new LocalizationOption("Russian", "ru"),
- new LocalizationOption("Slovak", "sk"),
- new LocalizationOption("Slovenian (Slovenia)", "sl-SI"),
- new LocalizationOption("Spanish", "es"),
- new LocalizationOption("Spanish (Argentina)", "es-AR"),
- new LocalizationOption("Spanish (Mexico)", "es-MX"),
- new LocalizationOption("Swedish", "sv"),
- new LocalizationOption("Swiss German", "gsw"),
- new LocalizationOption("Turkish", "tr")
- };
+ /// <inheritdoc />
+ public IEnumerable<LocalizationOption> GetLocalizationOptions()
+ {
+ yield return new LocalizationOption("Arabic", "ar");
+ yield return new LocalizationOption("Bulgarian (Bulgaria)", "bg-BG");
+ yield return new LocalizationOption("Catalan", "ca");
+ yield return new LocalizationOption("Chinese Simplified", "zh-CN");
+ yield return new LocalizationOption("Chinese Traditional", "zh-TW");
+ yield return new LocalizationOption("Croatian", "hr");
+ yield return new LocalizationOption("Czech", "cs");
+ yield return new LocalizationOption("Danish", "da");
+ yield return new LocalizationOption("Dutch", "nl");
+ yield return new LocalizationOption("English (United Kingdom)", "en-GB");
+ yield return new LocalizationOption("English (United States)", "en-US");
+ yield return new LocalizationOption("French", "fr");
+ yield return new LocalizationOption("French (Canada)", "fr-CA");
+ yield return new LocalizationOption("German", "de");
+ yield return new LocalizationOption("Greek", "el");
+ yield return new LocalizationOption("Hebrew", "he");
+ yield return new LocalizationOption("Hungarian", "hu");
+ yield return new LocalizationOption("Italian", "it");
+ yield return new LocalizationOption("Kazakh", "kk");
+ yield return new LocalizationOption("Korean", "ko");
+ yield return new LocalizationOption("Lithuanian", "lt-LT");
+ yield return new LocalizationOption("Malay", "ms");
+ yield return new LocalizationOption("Norwegian Bokmål", "nb");
+ yield return new LocalizationOption("Persian", "fa");
+ yield return new LocalizationOption("Polish", "pl");
+ yield return new LocalizationOption("Portuguese (Brazil)", "pt-BR");
+ yield return new LocalizationOption("Portuguese (Portugal)", "pt-PT");
+ yield return new LocalizationOption("Russian", "ru");
+ yield return new LocalizationOption("Slovak", "sk");
+ yield return new LocalizationOption("Slovenian (Slovenia)", "sl-SI");
+ yield return new LocalizationOption("Spanish", "es");
+ yield return new LocalizationOption("Spanish (Argentina)", "es-AR");
+ yield return new LocalizationOption("Spanish (Mexico)", "es-MX");
+ yield return new LocalizationOption("Swedish", "sv");
+ yield return new LocalizationOption("Swiss German", "gsw");
+ yield return new LocalizationOption("Turkish", "tr");
+ }
}
}
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 0347100a4..61329160a 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -1375,16 +1375,14 @@ namespace Emby.Server.Implementations.Session
CheckDisposed();
User user = null;
- if (!request.UserId.Equals(Guid.Empty))
+ if (request.UserId != Guid.Empty)
{
- user = _userManager.Users
- .FirstOrDefault(i => i.Id == request.UserId);
+ user = _userManager.GetUserById(request.UserId);
}
if (user == null)
{
- user = _userManager.Users
- .FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase));
+ user = _userManager.GetUserByName(request.Username);
}
if (user != null)
diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs
index dd313b336..e93bff124 100644
--- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs
+++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs
@@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.SocketSharp
/// <summary>
/// Releases the unmanaged resources and disposes of the managed resources used.
/// </summary>
- /// <param name="disposing">Whether or not the managed resources should be disposed</param>
+ /// <param name="disposing">Whether or not the managed resources should be disposed.</param>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
diff --git a/Emby.XmlTv/Emby.XmlTv/Emby.XmlTv.csproj b/Emby.XmlTv/Emby.XmlTv/Emby.XmlTv.csproj
index 0225be2c2..04f558173 100644
--- a/Emby.XmlTv/Emby.XmlTv/Emby.XmlTv.csproj
+++ b/Emby.XmlTv/Emby.XmlTv/Emby.XmlTv.csproj
@@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
index f023bc55d..396bdd4b7 100644
--- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
+++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
@@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs
index 23c7339d2..6d3037b24 100644
--- a/MediaBrowser.Api/Images/ImageService.cs
+++ b/MediaBrowser.Api/Images/ImageService.cs
@@ -550,14 +550,14 @@ namespace MediaBrowser.Api.Images
}
IImageEnhancer[] supportedImageEnhancers;
- if (_imageProcessor.ImageEnhancers.Length > 0)
+ if (_imageProcessor.ImageEnhancers.Count > 0)
{
if (item == null)
{
item = _libraryManager.GetItemById(itemId);
}
- supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.GetSupportedEnhancers(item, request.Type) : Array.Empty<IImageEnhancer>();
+ supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.GetSupportedEnhancers(item, request.Type).ToArray() : Array.Empty<IImageEnhancer>();
}
else
{
@@ -606,8 +606,8 @@ namespace MediaBrowser.Api.Images
ImageRequest request,
ItemImageInfo image,
bool cropwhitespace,
- ImageFormat[] supportedFormats,
- IImageEnhancer[] enhancers,
+ IReadOnlyCollection<ImageFormat> supportedFormats,
+ IReadOnlyCollection<IImageEnhancer> enhancers,
TimeSpan? cacheDuration,
IDictionary<string, string> headers,
bool isHeadRequest)
diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs
index 825732888..d6514d62e 100644
--- a/MediaBrowser.Api/ItemUpdateService.cs
+++ b/MediaBrowser.Api/ItemUpdateService.cs
@@ -69,8 +69,8 @@ namespace MediaBrowser.Api
{
ParentalRatingOptions = _localizationManager.GetParentalRatings().ToArray(),
ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToArray(),
- Countries = await _localizationManager.GetCountries(),
- Cultures = _localizationManager.GetCultures()
+ Countries = _localizationManager.GetCountries().ToArray(),
+ Cultures = _localizationManager.GetCultures().ToArray()
};
if (!item.IsVirtualItem && !(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName) &&
diff --git a/MediaBrowser.Api/LocalizationService.cs b/MediaBrowser.Api/LocalizationService.cs
index eeff67e13..3b2e18852 100644
--- a/MediaBrowser.Api/LocalizationService.cs
+++ b/MediaBrowser.Api/LocalizationService.cs
@@ -1,4 +1,3 @@
-using System.Threading.Tasks;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
@@ -82,9 +81,9 @@ namespace MediaBrowser.Api
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public async Task<object> Get(GetCountries request)
+ public object Get(GetCountries request)
{
- var result = await _localization.GetCountries();
+ var result = _localization.GetCountries();
return ToOptimizedResult(result);
}
diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index ba29c656b..f653270a6 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -12,6 +12,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs
index fa70a52aa..21a94a4e0 100644
--- a/MediaBrowser.Api/UserService.cs
+++ b/MediaBrowser.Api/UserService.cs
@@ -365,8 +365,8 @@ namespace MediaBrowser.Api
}
_sessionMananger.RevokeUserTokens(user.Id, null);
-
- return _userManager.DeleteUser(user);
+ _userManager.DeleteUser(user);
+ return Task.CompletedTask;
}
/// <summary>
@@ -503,9 +503,14 @@ namespace MediaBrowser.Api
}
}
+ /// <summary>
+ /// Posts the specified request.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <returns>System.Object.</returns>
public async Task<object> Post(CreateUserByName request)
{
- var newUser = await _userManager.CreateUser(request.Name).ConfigureAwait(false);
+ var newUser = _userManager.CreateUser(request.Name);
// no need to authenticate password for new user
if (request.Password != null)
diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs
index cb7343440..2248e9c85 100644
--- a/MediaBrowser.Common/IApplicationHost.cs
+++ b/MediaBrowser.Common/IApplicationHost.cs
@@ -75,10 +75,10 @@ namespace MediaBrowser.Common
/// <summary>
/// Gets the exports.
/// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="manageLiftime">if set to <c>true</c> [manage liftime].</param>
- /// <returns>IEnumerable{``0}.</returns>
- IEnumerable<T> GetExports<T>(bool manageLifetime = true);
+ /// <typeparam name="T">The type.</typeparam>
+ /// <param name="manageLifetime">If set to <c>true</c> [manage lifetime].</param>
+ /// <returns><see cref="IReadOnlyCollection{T}" />.</returns>
+ IReadOnlyCollection<T> GetExports<T>(bool manageLifetime = true);
/// <summary>
/// Resolves this instance.
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 05b48a2a1..91ab066f9 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -23,6 +23,12 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <!-- We need at least C# 7.1 for the "default literal" feature-->
+ <LangVersion>latest</LangVersion>
</PropertyGroup>
</Project>
diff --git a/MediaBrowser.Controller/Authentication/AuthenticationException.cs b/MediaBrowser.Controller/Authentication/AuthenticationException.cs
index 045cbcdae..62eca3ea9 100644
--- a/MediaBrowser.Controller/Authentication/AuthenticationException.cs
+++ b/MediaBrowser.Controller/Authentication/AuthenticationException.cs
@@ -1,4 +1,5 @@
using System;
+
namespace MediaBrowser.Controller.Authentication
{
/// <summary>
diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs
index 4eaecd0a0..a0f9ae46e 100644
--- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs
+++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs
@@ -18,16 +18,6 @@ namespace MediaBrowser.Controller.Drawing
IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; }
/// <summary>
- /// Encodes the image.
- /// </summary>
- string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat);
-
- /// <summary>
- /// Creates the image collage.
- /// </summary>
- /// <param name="options">The options.</param>
- void CreateImageCollage(ImageCollageOptions options);
- /// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
@@ -46,5 +36,16 @@ namespace MediaBrowser.Controller.Drawing
bool SupportsImageEncoding { get; }
ImageDimensions GetImageSize(string path);
+
+ /// <summary>
+ /// Encodes the image.
+ /// </summary>
+ string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat);
+
+ /// <summary>
+ /// Creates the image collage.
+ /// </summary>
+ /// <param name="options">The options.</param>
+ void CreateImageCollage(ImageCollageOptions options);
}
}
diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
index a11e2186f..a58a11bd1 100644
--- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs
+++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
@@ -24,7 +24,15 @@ namespace MediaBrowser.Controller.Drawing
/// Gets the image enhancers.
/// </summary>
/// <value>The image enhancers.</value>
- IImageEnhancer[] ImageEnhancers { get; }
+ IReadOnlyCollection<IImageEnhancer> ImageEnhancers { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether [supports image collage creation].
+ /// </summary>
+ /// <value><c>true</c> if [supports image collage creation]; otherwise, <c>false</c>.</value>
+ bool SupportsImageCollageCreation { get; }
+
+ IImageEncoder ImageEncoder { get; set; }
/// <summary>
/// Gets the dimensions of the image.
@@ -51,18 +59,12 @@ namespace MediaBrowser.Controller.Drawing
ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem);
/// <summary>
- /// Adds the parts.
- /// </summary>
- /// <param name="enhancers">The enhancers.</param>
- void AddParts(IEnumerable<IImageEnhancer> enhancers);
-
- /// <summary>
/// Gets the supported enhancers.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <returns>IEnumerable{IImageEnhancer}.</returns>
- IImageEnhancer[] GetSupportedEnhancers(BaseItem item, ImageType imageType);
+ IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType);
/// <summary>
/// Gets the image cache tag.
@@ -80,7 +82,7 @@ namespace MediaBrowser.Controller.Drawing
/// <param name="image">The image.</param>
/// <param name="imageEnhancers">The image enhancers.</param>
/// <returns>Guid.</returns>
- string GetImageCacheTag(BaseItem item, ItemImageInfo image, IImageEnhancer[] imageEnhancers);
+ string GetImageCacheTag(BaseItem item, ItemImageInfo image, IReadOnlyCollection<IImageEnhancer> imageEnhancers);
/// <summary>
/// Processes the image.
@@ -109,7 +111,7 @@ namespace MediaBrowser.Controller.Drawing
/// <summary>
/// Gets the supported image output formats.
/// </summary>
- /// <returns>IReadOnlyCollection{ImageOutput}.</returns>
+ /// <returns><see cref="IReadOnlyCollection{ImageOutput}" />.</returns>
IReadOnlyCollection<ImageFormat> GetSupportedImageOutputFormats();
/// <summary>
@@ -118,14 +120,6 @@ namespace MediaBrowser.Controller.Drawing
/// <param name="options">The options.</param>
void CreateImageCollage(ImageCollageOptions options);
- /// <summary>
- /// Gets a value indicating whether [supports image collage creation].
- /// </summary>
- /// <value><c>true</c> if [supports image collage creation]; otherwise, <c>false</c>.</value>
- bool SupportsImageCollageCreation { get; }
-
- IImageEncoder ImageEncoder { get; set; }
-
bool SupportsTransparency(string path);
}
}
diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
index db432f500..29addf6e6 100644
--- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
+++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using MediaBrowser.Controller.Entities;
@@ -33,9 +34,9 @@ namespace MediaBrowser.Controller.Drawing
public int Quality { get; set; }
- public IImageEnhancer[] Enhancers { get; set; }
+ public IReadOnlyCollection<IImageEnhancer> Enhancers { get; set; }
- public ImageFormat[] SupportedOutputFormats { get; set; }
+ public IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; set; }
public bool AddPlayedIndicator { get; set; }
diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs
index 968d72579..7d245d4aa 100644
--- a/MediaBrowser.Controller/Entities/User.cs
+++ b/MediaBrowser.Controller/Entities/User.cs
@@ -17,13 +17,6 @@ namespace MediaBrowser.Controller.Entities
public class User : BaseItem
{
public static IUserManager UserManager { get; set; }
- public static IXmlSerializer XmlSerializer { get; set; }
-
- /// <summary>
- /// From now on all user paths will be Id-based.
- /// This is for backwards compatibility.
- /// </summary>
- public bool UsesIdForConfigurationPath { get; set; }
/// <summary>
/// Gets or sets the password.
@@ -31,7 +24,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The password.</value>
public string Password { get; set; }
public string EasyPassword { get; set; }
- public string Salt { get; set; }
// Strictly to remove IgnoreDataMember
public override ItemImageInfo[] ImageInfos
@@ -148,46 +140,23 @@ namespace MediaBrowser.Controller.Entities
/// <exception cref="ArgumentNullException"></exception>
public Task Rename(string newName)
{
- if (string.IsNullOrEmpty(newName))
- {
- throw new ArgumentNullException(nameof(newName));
- }
-
- // If only the casing is changing, leave the file system alone
- if (!UsesIdForConfigurationPath && !string.Equals(newName, Name, StringComparison.OrdinalIgnoreCase))
+ if (string.IsNullOrWhiteSpace(newName))
{
- UsesIdForConfigurationPath = true;
-
- // Move configuration
- var newConfigDirectory = GetConfigurationDirectoryPath(newName);
- var oldConfigurationDirectory = ConfigurationDirectoryPath;
-
- // Exceptions will be thrown if these paths already exist
- if (Directory.Exists(newConfigDirectory))
- {
- Directory.Delete(newConfigDirectory, true);
- }
-
- if (Directory.Exists(oldConfigurationDirectory))
- {
- Directory.Move(oldConfigurationDirectory, newConfigDirectory);
- }
- else
- {
- Directory.CreateDirectory(newConfigDirectory);
- }
+ throw new ArgumentException("Username can't be empty", nameof(newName));
}
Name = newName;
- return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem))
- {
- ReplaceAllMetadata = true,
- ImageRefreshMode = MetadataRefreshMode.FullRefresh,
- MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
- ForceSave = true
+ return RefreshMetadata(
+ new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem))
+ {
+ ReplaceAllMetadata = true,
+ ImageRefreshMode = MetadataRefreshMode.FullRefresh,
+ MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
+ ForceSave = true
- }, CancellationToken.None);
+ },
+ CancellationToken.None);
}
public override void UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken)
@@ -216,19 +185,6 @@ namespace MediaBrowser.Controller.Entities
{
var parentPath = ConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath;
- // Legacy
- if (!UsesIdForConfigurationPath)
- {
- if (string.IsNullOrEmpty(username))
- {
- throw new ArgumentNullException(nameof(username));
- }
-
- var safeFolderName = FileSystem.GetValidFilename(username);
-
- return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, safeFolderName);
- }
-
// 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", CultureInfo.InvariantCulture));
diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs
index 7f7370893..bbedc0442 100644
--- a/MediaBrowser.Controller/Library/IUserManager.cs
+++ b/MediaBrowser.Controller/Library/IUserManager.cs
@@ -23,6 +23,12 @@ namespace MediaBrowser.Controller.Library
IEnumerable<User> Users { get; }
/// <summary>
+ /// Gets the user ids.
+ /// </summary>
+ /// <value>The users ids.</value>
+ IEnumerable<Guid> UsersIds { get; }
+
+ /// <summary>
/// Occurs when [user updated].
/// </summary>
event EventHandler<GenericEventArgs<User>> UserUpdated;
@@ -92,7 +98,7 @@ namespace MediaBrowser.Controller.Library
/// <returns>User.</returns>
/// <exception cref="ArgumentNullException">name</exception>
/// <exception cref="ArgumentException"></exception>
- Task<User> CreateUser(string name);
+ User CreateUser(string name);
/// <summary>
/// Deletes the user.
@@ -101,7 +107,7 @@ namespace MediaBrowser.Controller.Library
/// <returns>Task.</returns>
/// <exception cref="ArgumentNullException">user</exception>
/// <exception cref="ArgumentException"></exception>
- Task DeleteUser(User user);
+ void DeleteUser(User user);
/// <summary>
/// Resets the password.
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 01893f1b5..c6bca2518 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -19,6 +19,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
index 867b82ede..a8f8da9b8 100644
--- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
+++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
@@ -12,6 +12,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 681a2e372..fdb20477f 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 2673597ca..d64ea35eb 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -163,6 +163,7 @@ namespace MediaBrowser.Model.Configuration
public string ServerName { get; set; }
public string WanDdns { get; set; }
+ public string BaseUrl { get; set; }
public string UICulture { get; set; }
@@ -243,6 +244,7 @@ namespace MediaBrowser.Model.Configuration
SortRemoveCharacters = new[] { ",", "&", "-", "{", "}", "'" };
SortRemoveWords = new[] { "the", "a", "an" };
+ BaseUrl = "jellyfin";
UICulture = "en-US";
MetadataOptions = new[]
diff --git a/MediaBrowser.Model/Globalization/CultureDto.cs b/MediaBrowser.Model/Globalization/CultureDto.cs
index f229f2055..a213d4147 100644
--- a/MediaBrowser.Model/Globalization/CultureDto.cs
+++ b/MediaBrowser.Model/Globalization/CultureDto.cs
@@ -38,6 +38,7 @@ namespace MediaBrowser.Model.Globalization
{
return vals[0];
}
+
return null;
}
}
diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs
index a9ce60a2a..91d946db8 100644
--- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs
+++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Globalization;
-using System.Threading.Tasks;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Globalization
@@ -13,23 +12,26 @@ namespace MediaBrowser.Model.Globalization
/// <summary>
/// Gets the cultures.
/// </summary>
- /// <returns>IEnumerable{CultureDto}.</returns>
- CultureDto[] GetCultures();
+ /// <returns><see cref="IEnumerable{CultureDto}" />.</returns>
+ IEnumerable<CultureDto> GetCultures();
+
/// <summary>
/// Gets the countries.
/// </summary>
- /// <returns>IEnumerable{CountryInfo}.</returns>
- Task<CountryInfo[]> GetCountries();
+ /// <returns><see cref="IEnumerable{CountryInfo}" />.</returns>
+ IEnumerable<CountryInfo> GetCountries();
+
/// <summary>
/// Gets the parental ratings.
/// </summary>
- /// <returns>IEnumerable{ParentalRating}.</returns>
+ /// <returns><see cref="IEnumerable{ParentalRating}" />.</returns>
IEnumerable<ParentalRating> GetParentalRatings();
+
/// <summary>
/// Gets the rating level.
/// </summary>
/// <param name="rating">The rating.</param>
- /// <returns>System.Int32.</returns>
+ /// <returns><see cref="int" /> or <c>null</c>.</returns>
int? GetRatingLevel(string rating);
/// <summary>
@@ -37,7 +39,7 @@ namespace MediaBrowser.Model.Globalization
/// </summary>
/// <param name="phrase">The phrase.</param>
/// <param name="culture">The culture.</param>
- /// <returns>System.String.</returns>
+ /// <returns><see cref="string" />.</returns>
string GetLocalizedString(string phrase, string culture);
/// <summary>
@@ -50,13 +52,22 @@ namespace MediaBrowser.Model.Globalization
/// <summary>
/// Gets the localization options.
/// </summary>
- /// <returns>IEnumerable{LocalizatonOption}.</returns>
- LocalizationOption[] GetLocalizationOptions();
-
- string NormalizeFormKD(string text);
+ /// <returns><see cref="IEnumerable{LocalizatonOption}" />.</returns>
+ IEnumerable<LocalizationOption> GetLocalizationOptions();
+ /// <summary>
+ /// Checks if the string contains a character with the specified unicode category.
+ /// </summary>
+ /// <param name="value">The string.</param>
+ /// <param name="category">The unicode category.</param>
+ /// <returns>Wether or not the string contains a character with the specified unicode category.</returns>
bool HasUnicodeCategory(string value, UnicodeCategory category);
+ /// <summary>
+ /// Returns the correct <see cref="Cultureinfo" /> for the given language.
+ /// </summary>
+ /// <param name="language">The language.</param>
+ /// <returns>The correct <see cref="Cultureinfo" /> for the given language.</returns>
CultureDto FindLanguageInfo(string language);
}
}
diff --git a/MediaBrowser.Model/IO/IIsoManager.cs b/MediaBrowser.Model/IO/IIsoManager.cs
index 24b6e5f05..eb0cb4bfb 100644
--- a/MediaBrowser.Model/IO/IIsoManager.cs
+++ b/MediaBrowser.Model/IO/IIsoManager.cs
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Model.IO
{
- public interface IIsoManager : IDisposable
+ public interface IIsoManager
{
/// <summary>
/// Mounts the specified iso path.
diff --git a/MediaBrowser.Model/IO/IIsoMounter.cs b/MediaBrowser.Model/IO/IIsoMounter.cs
index f0153a928..766a9e4e6 100644
--- a/MediaBrowser.Model/IO/IIsoMounter.cs
+++ b/MediaBrowser.Model/IO/IIsoMounter.cs
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Model.IO
{
- public interface IIsoMounter : IDisposable
+ public interface IIsoMounter
{
/// <summary>
/// Mounts the specified iso path.
diff --git a/MediaBrowser.Model/IO/StreamDefaults.cs b/MediaBrowser.Model/IO/StreamDefaults.cs
index bef20e74f..1dc29e06e 100644
--- a/MediaBrowser.Model/IO/StreamDefaults.cs
+++ b/MediaBrowser.Model/IO/StreamDefaults.cs
@@ -13,6 +13,6 @@ namespace MediaBrowser.Model.IO
/// <summary>
/// The default file stream buffer size
/// </summary>
- public const int DefaultFileStreamBufferSize = 81920;
+ public const int DefaultFileStreamBufferSize = 4096;
}
}
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 3de2cca2d..e9f43ea56 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -10,6 +10,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
diff --git a/MediaBrowser.Model/Plugins/BasePluginConfiguration.cs b/MediaBrowser.Model/Plugins/BasePluginConfiguration.cs
index 39db22133..ac540782c 100644
--- a/MediaBrowser.Model/Plugins/BasePluginConfiguration.cs
+++ b/MediaBrowser.Model/Plugins/BasePluginConfiguration.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Model.Plugins
{
/// <summary>
- /// Class BasePluginConfiguration
+ /// Class BasePluginConfiguration.
/// </summary>
public class BasePluginConfiguration
{
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 5941ed436..ab4759c61 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -21,6 +21,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs
index c04e98e64..eaebc13e3 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs
@@ -50,27 +50,25 @@ namespace MediaBrowser.Providers.TV.TheTVDB
var language = item.GetPreferredMetadataLanguage();
if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
{
- var episodeTvdbId = episode.GetProviderId(MetadataProviders.Tvdb);
-
// Process images
try
{
+ var episodeInfo = new EpisodeInfo
+ {
+ IndexNumber = episode.IndexNumber.Value,
+ ParentIndexNumber = episode.ParentIndexNumber.Value,
+ SeriesProviderIds = series.ProviderIds
+ };
+ string episodeTvdbId = await _tvDbClientManager
+ .GetEpisodeTvdbId(episodeInfo, language, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(episodeTvdbId))
{
- var episodeInfo = new EpisodeInfo
- {
- IndexNumber = episode.IndexNumber.Value,
- ParentIndexNumber = episode.ParentIndexNumber.Value,
- SeriesProviderIds = series.ProviderIds
- };
- episodeTvdbId = await _tvDbClientManager
- .GetEpisodeTvdbId(episodeInfo, language, cancellationToken).ConfigureAwait(false);
- if (string.IsNullOrEmpty(episodeTvdbId))
- {
- _logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
- episodeInfo.ParentIndexNumber, episodeInfo.IndexNumber, series.GetProviderId(MetadataProviders.Tvdb));
- return imageResult;
- }
+ _logger.LogError(
+ "Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
+ episodeInfo.ParentIndexNumber,
+ episodeInfo.IndexNumber,
+ series.GetProviderId(MetadataProviders.Tvdb));
+ return imageResult;
}
var episodeResult =
@@ -86,7 +84,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
}
catch (TvDbServerException e)
{
- _logger.LogError(e, "Failed to retrieve episode images for {TvDbId}", episodeTvdbId);
+ _logger.LogError(e, "Failed to retrieve episode images for series {TvDbId}", series.GetProviderId(MetadataProviders.Tvdb));
}
}
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
index 302d40c6b..e5287048d 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
@@ -36,57 +36,33 @@ namespace MediaBrowser.Providers.TV.TheTVDB
var list = new List<RemoteSearchResult>();
// The search query must either provide an episode number or date
- if (!searchInfo.IndexNumber.HasValue || !searchInfo.PremiereDate.HasValue)
+ if (!searchInfo.IndexNumber.HasValue
+ || !searchInfo.PremiereDate.HasValue
+ || !TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds))
{
return list;
}
- if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds))
+ var metadataResult = await GetEpisode(searchInfo, cancellationToken).ConfigureAwait(false);
+
+ if (!metadataResult.HasMetadata)
{
- try
- {
- var episodeTvdbId = searchInfo.GetProviderId(MetadataProviders.Tvdb);
- if (string.IsNullOrEmpty(episodeTvdbId))
- {
- searchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(),
- out var seriesTvdbId);
- episodeTvdbId = await _tvDbClientManager
- .GetEpisodeTvdbId(searchInfo, searchInfo.MetadataLanguage, cancellationToken)
- .ConfigureAwait(false);
- if (string.IsNullOrEmpty(episodeTvdbId))
- {
- _logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
- searchInfo.ParentIndexNumber, searchInfo.IndexNumber, seriesTvdbId);
- return list;
- }
- }
+ return list;
+ }
- var episodeResult = await _tvDbClientManager.GetEpisodesAsync(Convert.ToInt32(episodeTvdbId),
- searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);
- var metadataResult = MapEpisodeToResult(searchInfo, episodeResult.Data);
+ var item = metadataResult.Item;
- if (metadataResult.HasMetadata)
- {
- var item = metadataResult.Item;
-
- list.Add(new RemoteSearchResult
- {
- IndexNumber = item.IndexNumber,
- Name = item.Name,
- ParentIndexNumber = item.ParentIndexNumber,
- PremiereDate = item.PremiereDate,
- ProductionYear = item.ProductionYear,
- ProviderIds = item.ProviderIds,
- SearchProviderName = Name,
- IndexNumberEnd = item.IndexNumberEnd
- });
- }
- }
- catch (TvDbServerException e)
- {
- _logger.LogError(e, "Failed to retrieve episode with id {TvDbId}", searchInfo.IndexNumber);
- }
- }
+ list.Add(new RemoteSearchResult
+ {
+ IndexNumber = item.IndexNumber,
+ Name = item.Name,
+ ParentIndexNumber = item.ParentIndexNumber,
+ PremiereDate = item.PremiereDate,
+ ProductionYear = item.ProductionYear,
+ ProviderIds = item.ProviderIds,
+ SearchProviderName = Name,
+ IndexNumberEnd = item.IndexNumberEnd
+ });
return list;
}
@@ -103,36 +79,46 @@ namespace MediaBrowser.Providers.TV.TheTVDB
if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds) &&
(searchInfo.IndexNumber.HasValue || searchInfo.PremiereDate.HasValue))
{
- var tvdbId = searchInfo.GetProviderId(MetadataProviders.Tvdb);
- try
- {
- if (string.IsNullOrEmpty(tvdbId))
- {
- tvdbId = await _tvDbClientManager
- .GetEpisodeTvdbId(searchInfo, searchInfo.MetadataLanguage, cancellationToken)
- .ConfigureAwait(false);
- if (string.IsNullOrEmpty(tvdbId))
- {
- _logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
- searchInfo.ParentIndexNumber, searchInfo.IndexNumber, tvdbId);
- return result;
- }
- }
+ result = await GetEpisode(searchInfo, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ _logger.LogDebug("No series identity found for {EpisodeName}", searchInfo.Name);
+ }
- var episodeResult = await _tvDbClientManager.GetEpisodesAsync(
- Convert.ToInt32(tvdbId), searchInfo.MetadataLanguage,
- cancellationToken).ConfigureAwait(false);
+ return result;
+ }
- result = MapEpisodeToResult(searchInfo, episodeResult.Data);
- }
- catch (TvDbServerException e)
+ private async Task<MetadataResult<Episode>> GetEpisode(EpisodeInfo searchInfo, CancellationToken cancellationToken)
+ {
+ var result = new MetadataResult<Episode>
+ {
+ QueriedById = true
+ };
+
+ string seriesTvdbId = searchInfo.GetProviderId(MetadataProviders.Tvdb);
+ string episodeTvdbId = null;
+ try
+ {
+ episodeTvdbId = await _tvDbClientManager
+ .GetEpisodeTvdbId(searchInfo, searchInfo.MetadataLanguage, cancellationToken)
+ .ConfigureAwait(false);
+ if (string.IsNullOrEmpty(episodeTvdbId))
{
- _logger.LogError(e, "Failed to retrieve episode with id {TvDbId}", tvdbId);
+ _logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
+ searchInfo.ParentIndexNumber, searchInfo.IndexNumber, seriesTvdbId);
+ return result;
}
+
+ var episodeResult = await _tvDbClientManager.GetEpisodesAsync(
+ Convert.ToInt32(episodeTvdbId), searchInfo.MetadataLanguage,
+ cancellationToken).ConfigureAwait(false);
+
+ result = MapEpisodeToResult(searchInfo, episodeResult.Data);
}
- else
+ catch (TvDbServerException e)
{
- _logger.LogDebug("No series identity found for {EpisodeName}", searchInfo.Name);
+ _logger.LogError(e, "Failed to retrieve episode with id {EpisodeTvDbId}, series id {SeriesTvdbId}", episodeTvdbId, seriesTvdbId);
}
return result;
@@ -193,24 +179,54 @@ namespace MediaBrowser.Providers.TV.TheTVDB
});
}
- foreach (var person in episode.GuestStars)
+ // GuestStars is a weird list of names and roles
+ // Example:
+ // 1: Some Actor (Role1
+ // 2: Role2
+ // 3: Role3)
+ // 4: Another Actor (Role1
+ // ...
+ for (var i = 0; i < episode.GuestStars.Length; ++i)
{
- var index = person.IndexOf('(');
- string role = null;
- var name = person;
+ var currentActor = episode.GuestStars[i];
+ var roleStartIndex = currentActor.IndexOf('(');
- if (index != -1)
+ if (roleStartIndex == -1)
{
- role = person.Substring(index + 1).Trim().TrimEnd(')');
+ result.AddPerson(new PersonInfo
+ {
+ Type = PersonType.GuestStar,
+ Name = currentActor,
+ Role = string.Empty
+ });
+ continue;
+ }
+
+ var roles = new List<string> {currentActor.Substring(roleStartIndex + 1)};
+
+ // Fetch all roles
+ for (var j = i + 1; j < episode.GuestStars.Length; ++j)
+ {
+ var currentRole = episode.GuestStars[j];
+ var roleEndIndex = currentRole.IndexOf(')');
+
+ if (roleEndIndex == -1)
+ {
+ roles.Add(currentRole);
+ continue;
+ }
- name = person.Substring(0, index).Trim();
+ roles.Add(currentRole.TrimEnd(')'));
+ // Update the outer index (keep in mind it adds 1 after the iteration)
+ i = j;
+ break;
}
result.AddPerson(new PersonInfo
{
Type = PersonType.GuestStar,
- Name = name,
- Role = role
+ Name = currentActor.Substring(0, roleStartIndex).Trim(),
+ Role = string.Join(", ", roles)
});
}
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
index c739f3f49..1578e4341 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
@@ -285,7 +285,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
private string GetComparableName(string name)
{
name = name.ToLowerInvariant();
- name = _localizationManager.NormalizeFormKD(name);
+ name = name.Normalize(NormalizationForm.FormKD);
var sb = new StringBuilder();
foreach (var c in name)
{
@@ -310,19 +310,16 @@ namespace MediaBrowser.Providers.TV.TheTVDB
sb.Append(c);
}
}
- name = sb.ToString();
- name = name.Replace(", the", "");
- name = name.Replace("the ", " ");
- name = name.Replace(" the ", " ");
+ sb.Replace(", the", string.Empty).Replace("the ", " ").Replace(" the ", " ");
- string prevName;
+ int prevLength;
do
{
- prevName = name;
- name = name.Replace(" ", " ");
- } while (name.Length != prevName.Length);
+ prevLength = sb.Length;
+ sb.Replace(" ", " ");
+ } while (name.Length != prevLength);
- return name.Trim();
+ return sb.ToString().Trim();
}
private void MapSeriesToResult(MetadataResult<Series> result, TvDbSharper.Dto.Series tvdbSeries, string metadataLanguage)
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index c099e77d6..883986894 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -18,6 +18,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
index ba29c656b..f653270a6 100644
--- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
+++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
@@ -12,6 +12,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>