mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-11-15 18:08:53 -07:00
commit
697aee5b0c
@ -17,4 +17,16 @@
|
||||
<Compile Include="..\SharedVersion.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code analysers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller;
|
||||
@ -11,7 +10,6 @@ using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
@ -23,7 +21,7 @@ namespace Emby.Drawing
|
||||
/// <summary>
|
||||
/// Class ImageProcessor.
|
||||
/// </summary>
|
||||
public class ImageProcessor : IImageProcessor, IDisposable
|
||||
public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
{
|
||||
// Increment this when there's a change requiring caches to be invalidated
|
||||
private const string Version = "3";
|
||||
@ -31,28 +29,24 @@ namespace Emby.Drawing
|
||||
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 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>
|
||||
///
|
||||
/// Initializes a new instance of the <see cref="ImageProcessor"/> class.
|
||||
/// </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>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="appPaths">The server application paths.</param>
|
||||
/// <param name="fileSystem">The filesystem.</param>
|
||||
/// <param name="imageEncoder">The image encoder.</param>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="mediaEncoder">The media encoder.</param>
|
||||
public ImageProcessor(
|
||||
ILogger<ImageProcessor> logger,
|
||||
IServerApplicationPaths appPaths,
|
||||
@ -67,16 +61,10 @@ namespace Emby.Drawing
|
||||
_libraryManager = libraryManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_appPaths = appPaths;
|
||||
|
||||
ImageEnhancers = Array.Empty<IImageEnhancer>();
|
||||
|
||||
ImageHelper.ImageProcessor = this;
|
||||
}
|
||||
|
||||
private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images");
|
||||
|
||||
private string EnhancedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "enhanced-images");
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyCollection<string> SupportedInputFormats =>
|
||||
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
@ -89,9 +77,7 @@ namespace Emby.Drawing
|
||||
"aiff",
|
||||
"cr2",
|
||||
"crw",
|
||||
|
||||
// Remove until supported
|
||||
//"nef",
|
||||
"nef",
|
||||
"orf",
|
||||
"pef",
|
||||
"arw",
|
||||
@ -110,19 +96,9 @@ namespace Emby.Drawing
|
||||
"wbmp"
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyCollection<IImageEnhancer> ImageEnhancers { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SupportsImageCollageCreation => _imageEncoder.SupportsImageCollageCreation;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IImageEncoder ImageEncoder
|
||||
{
|
||||
get => _imageEncoder;
|
||||
set => _imageEncoder = value ?? throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
|
||||
{
|
||||
@ -150,6 +126,8 @@ namespace Emby.Drawing
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
var libraryManager = _libraryManager();
|
||||
|
||||
ItemImageInfo originalImage = options.Image;
|
||||
BaseItem item = options.Item;
|
||||
|
||||
@ -157,9 +135,10 @@ namespace Emby.Drawing
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
item = _libraryManager().GetItemById(options.ItemId);
|
||||
item = libraryManager.GetItemById(options.ItemId);
|
||||
}
|
||||
originalImage = await _libraryManager().ConvertImageToLocal(item, originalImage, options.ImageIndex).ConfigureAwait(false);
|
||||
|
||||
originalImage = await libraryManager.ConvertImageToLocal(item, originalImage, options.ImageIndex).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
string originalImagePath = originalImage.Path;
|
||||
@ -186,27 +165,6 @@ namespace Emby.Drawing
|
||||
dateModified = supportedImageInfo.dateModified;
|
||||
bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath));
|
||||
|
||||
if (options.Enhancers.Count > 0)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
item = _libraryManager().GetItemById(options.ItemId);
|
||||
}
|
||||
|
||||
var tuple = await GetEnhancedImage(new ItemImageInfo
|
||||
{
|
||||
DateModified = dateModified,
|
||||
Type = originalImage.Type,
|
||||
Path = originalImagePath
|
||||
}, requiresTransparency, item, options.ImageIndex, options.Enhancers, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
originalImagePath = tuple.path;
|
||||
dateModified = tuple.dateModified;
|
||||
requiresTransparency = tuple.transparent;
|
||||
// TODO: Get this info
|
||||
originalImageSize = null;
|
||||
}
|
||||
|
||||
bool autoOrient = false;
|
||||
ImageOrientation? orientation = null;
|
||||
if (item is Photo photo)
|
||||
@ -239,12 +197,6 @@ namespace Emby.Drawing
|
||||
ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency);
|
||||
string cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer);
|
||||
|
||||
CheckDisposed();
|
||||
|
||||
LockInfo lockInfo = GetLock(cacheFilePath);
|
||||
|
||||
await lockInfo.Lock.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
if (!File.Exists(cacheFilePath))
|
||||
@ -270,10 +222,6 @@ namespace Emby.Drawing
|
||||
_logger.LogError(ex, "Error encoding image");
|
||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLock(cacheFilePath, lockInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private ImageFormat GetOutputFormat(IReadOnlyCollection<ImageFormat> clientSupportedFormats, bool requiresTransparency)
|
||||
@ -305,20 +253,18 @@ namespace Emby.Drawing
|
||||
}
|
||||
|
||||
private string GetMimeType(ImageFormat format, string path)
|
||||
{
|
||||
switch(format)
|
||||
=> format switch
|
||||
{
|
||||
case ImageFormat.Bmp: return MimeTypes.GetMimeType("i.bmp");
|
||||
case ImageFormat.Gif: return MimeTypes.GetMimeType("i.gif");
|
||||
case ImageFormat.Jpg: return MimeTypes.GetMimeType("i.jpg");
|
||||
case ImageFormat.Png: return MimeTypes.GetMimeType("i.png");
|
||||
case ImageFormat.Webp: return MimeTypes.GetMimeType("i.webp");
|
||||
default: return MimeTypes.GetMimeType(path);
|
||||
}
|
||||
}
|
||||
ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"),
|
||||
ImageFormat.Gif => MimeTypes.GetMimeType("i.gif"),
|
||||
ImageFormat.Jpg => MimeTypes.GetMimeType("i.jpg"),
|
||||
ImageFormat.Png => MimeTypes.GetMimeType("i.png"),
|
||||
ImageFormat.Webp => MimeTypes.GetMimeType("i.webp"),
|
||||
_ => MimeTypes.GetMimeType(path)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cache file path based on a set of parameters
|
||||
/// 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)
|
||||
{
|
||||
@ -400,11 +346,7 @@ namespace Emby.Drawing
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
|
||||
{
|
||||
var supportedEnhancers = GetSupportedEnhancers(item, image.Type).ToArray();
|
||||
|
||||
return GetImageCacheTag(item, image, supportedEnhancers);
|
||||
}
|
||||
=> (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetImageCacheTag(BaseItem item, ChapterInfo chapter)
|
||||
@ -424,26 +366,6 @@ namespace Emby.Drawing
|
||||
}
|
||||
}
|
||||
|
||||
/// <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.Count == 0)
|
||||
{
|
||||
return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
// Cache name is created with supported enhancers combined with the last config change so we pick up new config changes
|
||||
var cacheKeys = imageEnhancers.Select(i => i.GetConfigurationCacheKey(item, imageType)).ToList();
|
||||
cacheKeys.Add(originalImagePath + dateModified.Ticks);
|
||||
|
||||
return string.Join("|", cacheKeys).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
|
||||
{
|
||||
var inputFormat = Path.GetExtension(originalImagePath)
|
||||
@ -487,154 +409,6 @@ namespace Emby.Drawing
|
||||
return (originalImagePath, dateModified);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex)
|
||||
{
|
||||
var enhancers = GetSupportedEnhancers(item, imageType).ToArray();
|
||||
|
||||
ItemImageInfo imageInfo = item.GetImageInfo(imageType, imageIndex);
|
||||
|
||||
bool inputImageSupportsTransparency = SupportsTransparency(imageInfo.Path);
|
||||
|
||||
var result = await GetEnhancedImage(imageInfo, inputImageSupportsTransparency, item, imageIndex, enhancers, CancellationToken.None);
|
||||
|
||||
return result.path;
|
||||
}
|
||||
|
||||
private async Task<(string path, DateTime dateModified, bool transparent)> GetEnhancedImage(
|
||||
ItemImageInfo image,
|
||||
bool inputImageSupportsTransparency,
|
||||
BaseItem item,
|
||||
int imageIndex,
|
||||
IReadOnlyCollection<IImageEnhancer> enhancers,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var originalImagePath = image.Path;
|
||||
var dateModified = image.DateModified;
|
||||
var imageType = image.Type;
|
||||
|
||||
try
|
||||
{
|
||||
var cacheGuid = GetImageCacheTag(item, image, enhancers);
|
||||
|
||||
// Enhance if we have enhancers
|
||||
var enhancedImageInfo = await GetEnhancedImageInternal(originalImagePath, item, imageType, imageIndex, enhancers, cacheGuid, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
string enhancedImagePath = enhancedImageInfo.path;
|
||||
|
||||
// If the path changed update dateModified
|
||||
if (!string.Equals(enhancedImagePath, originalImagePath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var treatmentRequiresTransparency = enhancedImageInfo.transparent;
|
||||
|
||||
return (enhancedImagePath, _fileSystem.GetLastWriteTimeUtc(enhancedImagePath), treatmentRequiresTransparency);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error enhancing image");
|
||||
}
|
||||
|
||||
return (originalImagePath, dateModified, inputImageSupportsTransparency);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the enhanced image internal.
|
||||
/// </summary>
|
||||
/// <param name="originalImagePath">The original image path.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="imageType">Type of the image.</param>
|
||||
/// <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<System.String>.</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// originalImagePath
|
||||
/// or
|
||||
/// item
|
||||
/// </exception>
|
||||
private async Task<(string path, bool transparent)> GetEnhancedImageInternal(
|
||||
string originalImagePath,
|
||||
BaseItem item,
|
||||
ImageType imageType,
|
||||
int imageIndex,
|
||||
IReadOnlyCollection<IImageEnhancer> supportedEnhancers,
|
||||
string cacheGuid,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrEmpty(originalImagePath))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(originalImagePath));
|
||||
}
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
var treatmentRequiresTransparency = false;
|
||||
foreach (var enhancer in supportedEnhancers)
|
||||
{
|
||||
if (!treatmentRequiresTransparency)
|
||||
{
|
||||
treatmentRequiresTransparency = enhancer.GetEnhancedImageInfo(item, originalImagePath, imageType, imageIndex).RequiresTransparency;
|
||||
}
|
||||
}
|
||||
|
||||
// All enhanced images are saved as png to allow transparency
|
||||
string cacheExtension = _imageEncoder.SupportedOutputFormats.Contains(ImageFormat.Webp) ?
|
||||
".webp" :
|
||||
(treatmentRequiresTransparency ? ".png" : ".jpg");
|
||||
|
||||
string enhancedImagePath = GetCachePath(EnhancedImageCachePath, cacheGuid + cacheExtension);
|
||||
|
||||
LockInfo lockInfo = GetLock(enhancedImagePath);
|
||||
|
||||
await lockInfo.Lock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
// Check again in case of contention
|
||||
if (File.Exists(enhancedImagePath))
|
||||
{
|
||||
return (enhancedImagePath, treatmentRequiresTransparency);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(enhancedImagePath));
|
||||
|
||||
await ExecuteImageEnhancers(supportedEnhancers, originalImagePath, enhancedImagePath, item, imageType, imageIndex).ConfigureAwait(false);
|
||||
|
||||
return (enhancedImagePath, treatmentRequiresTransparency);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLock(enhancedImagePath, lockInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the image enhancers.
|
||||
/// </summary>
|
||||
/// <param name="imageEnhancers">The image enhancers.</param>
|
||||
/// <param name="inputPath">The input path.</param>
|
||||
/// <param name="outputPath">The output path.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="imageType">Type of the image.</param>
|
||||
/// <param name="imageIndex">Index of the image.</param>
|
||||
/// <returns>Task{EnhancedImage}.</returns>
|
||||
private static async Task ExecuteImageEnhancers(IEnumerable<IImageEnhancer> imageEnhancers, string inputPath, string outputPath, BaseItem item, ImageType imageType, int imageIndex)
|
||||
{
|
||||
// Run the enhancers sequentially in order of priority
|
||||
foreach (var enhancer in imageEnhancers)
|
||||
{
|
||||
await enhancer.EnhanceImageAsync(item, inputPath, outputPath, imageType, imageIndex).ConfigureAwait(false);
|
||||
|
||||
// Feed the output into the next enhancer as input
|
||||
inputPath = outputPath;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cache path.
|
||||
/// </summary>
|
||||
@ -647,7 +421,7 @@ namespace Emby.Drawing
|
||||
/// or
|
||||
/// uniqueName
|
||||
/// or
|
||||
/// fileExtension
|
||||
/// fileExtension.
|
||||
/// </exception>
|
||||
public string GetCachePath(string path, string uniqueName, string fileExtension)
|
||||
{
|
||||
@ -680,7 +454,7 @@ namespace Emby.Drawing
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// path
|
||||
/// or
|
||||
/// filename
|
||||
/// filename.
|
||||
/// </exception>
|
||||
public string GetCachePath(string path, string filename)
|
||||
{
|
||||
@ -688,6 +462,7 @@ namespace Emby.Drawing
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filename));
|
||||
@ -708,75 +483,20 @@ namespace Emby.Drawing
|
||||
_logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType)
|
||||
{
|
||||
foreach (var i in ImageEnhancers)
|
||||
{
|
||||
if (i.Supports(item, imageType))
|
||||
{
|
||||
yield return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class LockInfo
|
||||
{
|
||||
public SemaphoreSlim Lock = new SemaphoreSlim(1, 1);
|
||||
public int Count = 1;
|
||||
}
|
||||
|
||||
private LockInfo GetLock(string key)
|
||||
{
|
||||
lock (_locks)
|
||||
{
|
||||
if (_locks.TryGetValue(key, out LockInfo info))
|
||||
{
|
||||
info.Count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
info = new LockInfo();
|
||||
_locks[key] = info;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReleaseLock(string key, LockInfo info)
|
||||
{
|
||||
info.Lock.Release();
|
||||
|
||||
lock (_locks)
|
||||
{
|
||||
info.Count--;
|
||||
if (info.Count <= 0)
|
||||
{
|
||||
_locks.Remove(key);
|
||||
info.Lock.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
var disposable = _imageEncoder as IDisposable;
|
||||
if (disposable != null)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckDisposed()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_imageEncoder is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,19 +8,12 @@ using Emby.Naming.Common;
|
||||
|
||||
namespace Emby.Naming.Audio
|
||||
{
|
||||
public class AudioFileParser
|
||||
public static class AudioFileParser
|
||||
{
|
||||
private readonly NamingOptions _options;
|
||||
|
||||
public AudioFileParser(NamingOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public bool IsAudioFile(string path)
|
||||
public static bool IsAudioFile(string path, NamingOptions options)
|
||||
{
|
||||
var extension = Path.GetExtension(path) ?? string.Empty;
|
||||
return _options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
||||
return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -277,7 +277,7 @@ namespace Emby.Naming.Common
|
||||
// This isn't a Kodi naming rule, but the expression below causes false positives,
|
||||
// so we make sure this one gets tested first.
|
||||
// "Foo Bar 889"
|
||||
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$")
|
||||
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/x]*$")
|
||||
{
|
||||
IsNamed = true
|
||||
},
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Emby.Naming.TV
|
||||
{
|
||||
@ -29,14 +28,14 @@ namespace Emby.Naming.TV
|
||||
{
|
||||
var result = new SeasonPathParserResult();
|
||||
|
||||
var seasonNumberInfo = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders);
|
||||
var (seasonNumber, isSeasonFolder) = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders);
|
||||
|
||||
result.SeasonNumber = seasonNumberInfo.seasonNumber;
|
||||
result.SeasonNumber = seasonNumber;
|
||||
|
||||
if (result.SeasonNumber.HasValue)
|
||||
{
|
||||
result.Success = true;
|
||||
result.IsSeasonFolder = seasonNumberInfo.isSeasonFolder;
|
||||
result.IsSeasonFolder = isSeasonFolder;
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -90,12 +89,10 @@ namespace Emby.Naming.TV
|
||||
// Look for one of the season folder names
|
||||
foreach (var name in _seasonFolderNames)
|
||||
{
|
||||
var index = filename.IndexOf(name, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (index != -1)
|
||||
if (filename.Contains(name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase));
|
||||
if (result.Item1.HasValue)
|
||||
if (result.seasonNumber.HasValue)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
@ -105,25 +102,32 @@ namespace Emby.Naming.TV
|
||||
}
|
||||
|
||||
var parts = filename.Split(new[] { '.', '_', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var resultNumber = parts.Select(GetSeasonNumberFromPart).FirstOrDefault(i => i.HasValue);
|
||||
return (resultNumber, true);
|
||||
for (int i = 0; i < parts.Length; i++)
|
||||
{
|
||||
if (TryGetSeasonNumberFromPart(parts[i], out int seasonNumber))
|
||||
{
|
||||
return (seasonNumber, true);
|
||||
}
|
||||
}
|
||||
|
||||
return (null, true);
|
||||
}
|
||||
|
||||
private static int? GetSeasonNumberFromPart(string part)
|
||||
private static bool TryGetSeasonNumberFromPart(ReadOnlySpan<char> part, out int seasonNumber)
|
||||
{
|
||||
seasonNumber = 0;
|
||||
if (part.Length < 2 || !part.StartsWith("s", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
part = part.Substring(1);
|
||||
|
||||
if (int.TryParse(part, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
|
||||
if (int.TryParse(part.Slice(1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
|
||||
{
|
||||
return value;
|
||||
seasonNumber = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -131,7 +135,7 @@ namespace Emby.Naming.TV
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>System.Nullable{System.Int32}.</returns>
|
||||
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(string path)
|
||||
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path)
|
||||
{
|
||||
var numericStart = -1;
|
||||
var length = 0;
|
||||
@ -142,7 +146,7 @@ namespace Emby.Naming.TV
|
||||
// Find out where the numbers start, and then keep going until they end
|
||||
for (var i = 0; i < path.Length; i++)
|
||||
{
|
||||
if (char.IsNumber(path, i))
|
||||
if (char.IsNumber(path[i]))
|
||||
{
|
||||
if (!hasOpenParenth)
|
||||
{
|
||||
@ -177,7 +181,7 @@ namespace Emby.Naming.TV
|
||||
return (null, isSeasonFolder);
|
||||
}
|
||||
|
||||
return (int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture), isSeasonFolder);
|
||||
return (int.Parse(path.Slice(numericStart, length), provider: CultureInfo.InvariantCulture), isSeasonFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ namespace Emby.Naming.Video
|
||||
|
||||
if (rule.MediaType == MediaType.Audio)
|
||||
{
|
||||
if (!new AudioFileParser(_options).IsAudioFile(path))
|
||||
if (!AudioFileParser.IsAudioFile(path, _options))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ namespace Emby.Naming.Video
|
||||
}
|
||||
}
|
||||
|
||||
private string GetRegexInput(FileSystemMetadata file)
|
||||
private static string GetRegexInput(FileSystemMetadata file)
|
||||
{
|
||||
// For directories, dummy up an extension otherwise the expressions will fail
|
||||
var input = !file.IsDirectory
|
||||
@ -204,7 +204,7 @@ namespace Emby.Naming.Video
|
||||
return Path.GetFileName(input);
|
||||
}
|
||||
|
||||
private Match FindMatch(FileSystemMetadata input, Regex regex, int offset)
|
||||
private static Match FindMatch(FileSystemMetadata input, Regex regex, int offset)
|
||||
{
|
||||
var regexInput = GetRegexInput(input);
|
||||
|
||||
|
@ -1,5 +1,11 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1402
|
||||
#pragma warning disable SA1600
|
||||
#pragma warning disable SA1649
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -16,7 +22,7 @@ namespace Emby.Notifications.Api
|
||||
public class GetNotifications : IReturn<NotificationResult>
|
||||
{
|
||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string UserId { get; set; }
|
||||
public string UserId { get; set; } = string.Empty;
|
||||
|
||||
[ApiMember(Name = "IsRead", Description = "An optional filter by IsRead", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||
public bool? IsRead { get; set; }
|
||||
@ -30,32 +36,34 @@ namespace Emby.Notifications.Api
|
||||
|
||||
public class Notification
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
public string UserId { get; set; }
|
||||
public string UserId { get; set; } = string.Empty;
|
||||
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
public bool IsRead { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Description { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public string Url { get; set; }
|
||||
public string Url { get; set; } = string.Empty;
|
||||
|
||||
public NotificationLevel Level { get; set; }
|
||||
}
|
||||
|
||||
public class NotificationResult
|
||||
{
|
||||
public Notification[] Notifications { get; set; }
|
||||
public IReadOnlyList<Notification> Notifications { get; set; } = Array.Empty<Notification>();
|
||||
|
||||
public int TotalRecordCount { get; set; }
|
||||
}
|
||||
|
||||
public class NotificationsSummary
|
||||
{
|
||||
public int UnreadCount { get; set; }
|
||||
|
||||
public NotificationLevel MaxUnreadNotificationLevel { get; set; }
|
||||
}
|
||||
|
||||
@ -63,7 +71,7 @@ namespace Emby.Notifications.Api
|
||||
public class GetNotificationsSummary : IReturn<NotificationsSummary>
|
||||
{
|
||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string UserId { get; set; }
|
||||
public string UserId { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[Route("/Notifications/Types", "GET", Summary = "Gets notification types")]
|
||||
@ -80,16 +88,16 @@ namespace Emby.Notifications.Api
|
||||
public class AddAdminNotification : IReturnVoid
|
||||
{
|
||||
[ApiMember(Name = "Name", Description = "The notification's name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public string Name { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[ApiMember(Name = "Description", Description = "The notification's description", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public string Description { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
[ApiMember(Name = "ImageUrl", Description = "The notification's image url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public string ImageUrl { get; set; }
|
||||
public string? ImageUrl { get; set; }
|
||||
|
||||
[ApiMember(Name = "Url", Description = "The notification's info url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public string Url { get; set; }
|
||||
public string? Url { get; set; }
|
||||
|
||||
[ApiMember(Name = "Level", Description = "The notification level", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public NotificationLevel Level { get; set; }
|
||||
@ -99,20 +107,20 @@ namespace Emby.Notifications.Api
|
||||
public class MarkRead : IReturnVoid
|
||||
{
|
||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string UserId { get; set; }
|
||||
public string UserId { get; set; } = string.Empty;
|
||||
|
||||
[ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
|
||||
public string Ids { get; set; }
|
||||
public string Ids { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[Route("/Notifications/{UserId}/Unread", "POST", Summary = "Marks notifications as unread")]
|
||||
public class MarkUnread : IReturnVoid
|
||||
{
|
||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string UserId { get; set; }
|
||||
public string UserId { get; set; } = string.Empty;
|
||||
|
||||
[ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
|
||||
public string Ids { get; set; }
|
||||
public string Ids { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[Authenticated]
|
||||
@ -127,32 +135,29 @@ namespace Emby.Notifications.Api
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
||||
public object Get(GetNotificationTypes request)
|
||||
{
|
||||
return _notificationManager.GetNotificationTypes();
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
||||
public object Get(GetNotificationServices request)
|
||||
{
|
||||
return _notificationManager.GetNotificationServices().ToList();
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
||||
public object Get(GetNotificationsSummary request)
|
||||
{
|
||||
return new NotificationsSummary
|
||||
{
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public Task Post(AddAdminNotification request)
|
||||
{
|
||||
// This endpoint really just exists as post of a real with sickbeard
|
||||
return AddNotification(request);
|
||||
}
|
||||
|
||||
private Task AddNotification(AddAdminNotification request)
|
||||
{
|
||||
var notification = new NotificationRequest
|
||||
{
|
||||
Date = DateTime.UtcNow,
|
||||
@ -166,14 +171,17 @@ namespace Emby.Notifications.Api
|
||||
return _notificationManager.SendNotification(notification, CancellationToken.None);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
||||
public void Post(MarkRead request)
|
||||
{
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
||||
public void Post(MarkUnread request)
|
||||
{
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
||||
public object Get(GetNotifications request)
|
||||
{
|
||||
return new NotificationResult();
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -4,6 +4,8 @@
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -16,4 +18,16 @@
|
||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Notifications;
|
||||
@ -13,7 +16,7 @@ namespace Emby.Notifications
|
||||
new ConfigurationStore
|
||||
{
|
||||
Key = "notifications",
|
||||
ConfigurationType = typeof (NotificationOptions)
|
||||
ConfigurationType = typeof(NotificationOptions)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -21,70 +21,85 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Emby.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates notifications for various system events
|
||||
/// Creates notifications for various system events.
|
||||
/// </summary>
|
||||
public class Notifications : IServerEntryPoint
|
||||
public class NotificationEntryPoint : IServerEntryPoint
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly IActivityManager _activityManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly INotificationManager _notificationManager;
|
||||
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
|
||||
private Timer LibraryUpdateTimer { get; set; }
|
||||
private readonly object _libraryChangedSyncLock = new object();
|
||||
|
||||
private readonly IConfigurationManager _config;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IActivityManager _activityManager;
|
||||
|
||||
private readonly object _libraryChangedSyncLock = new object();
|
||||
private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
|
||||
|
||||
private Timer? _libraryUpdateTimer;
|
||||
|
||||
private string[] _coreNotificationTypes;
|
||||
|
||||
public Notifications(
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NotificationEntryPoint" /> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="activityManager">The activity manager.</param>
|
||||
/// <param name="localization">The localization manager.</param>
|
||||
/// <param name="notificationManager">The notification manager.</param>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="appHost">The application host.</param>
|
||||
/// <param name="config">The configuration manager.</param>
|
||||
public NotificationEntryPoint(
|
||||
ILogger<NotificationEntryPoint> logger,
|
||||
IActivityManager activityManager,
|
||||
ILocalizationManager localization,
|
||||
ILogger logger,
|
||||
INotificationManager notificationManager,
|
||||
ILibraryManager libraryManager,
|
||||
IServerApplicationHost appHost,
|
||||
IConfigurationManager config)
|
||||
{
|
||||
_logger = logger;
|
||||
_activityManager = activityManager;
|
||||
_localization = localization;
|
||||
_notificationManager = notificationManager;
|
||||
_libraryManager = libraryManager;
|
||||
_appHost = appHost;
|
||||
_config = config;
|
||||
_localization = localization;
|
||||
_activityManager = activityManager;
|
||||
|
||||
_coreNotificationTypes = new CoreNotificationTypes(localization).GetNotificationTypes().Select(i => i.Type).ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RunAsync()
|
||||
{
|
||||
_libraryManager.ItemAdded += _libraryManager_ItemAdded;
|
||||
_appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged;
|
||||
_appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged;
|
||||
_activityManager.EntryCreated += _activityManager_EntryCreated;
|
||||
_libraryManager.ItemAdded += OnLibraryManagerItemAdded;
|
||||
_appHost.HasPendingRestartChanged += OnAppHostHasPendingRestartChanged;
|
||||
_appHost.HasUpdateAvailableChanged += OnAppHostHasUpdateAvailableChanged;
|
||||
_activityManager.EntryCreated += OnActivityManagerEntryCreated;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async void _appHost_HasPendingRestartChanged(object sender, EventArgs e)
|
||||
private async void OnAppHostHasPendingRestartChanged(object sender, EventArgs e)
|
||||
{
|
||||
var type = NotificationType.ServerRestartRequired.ToString();
|
||||
|
||||
var notification = new NotificationRequest
|
||||
{
|
||||
NotificationType = type,
|
||||
Name = string.Format(_localization.GetLocalizedString("ServerNameNeedsToBeRestarted"), _appHost.Name)
|
||||
Name = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("ServerNameNeedsToBeRestarted"),
|
||||
_appHost.Name)
|
||||
};
|
||||
|
||||
await SendNotification(notification, null).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void _activityManager_EntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e)
|
||||
private async void OnActivityManagerEntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e)
|
||||
{
|
||||
var entry = e.Argument;
|
||||
|
||||
@ -117,7 +132,7 @@ namespace Emby.Notifications
|
||||
return _config.GetConfiguration<NotificationOptions>("notifications");
|
||||
}
|
||||
|
||||
private async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e)
|
||||
private async void OnAppHostHasUpdateAvailableChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (!_appHost.HasUpdateAvailable)
|
||||
{
|
||||
@ -136,8 +151,7 @@ namespace Emby.Notifications
|
||||
await SendNotification(notification, null).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
|
||||
private void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
|
||||
private void OnLibraryManagerItemAdded(object sender, ItemChangeEventArgs e)
|
||||
{
|
||||
if (!FilterItem(e.Item))
|
||||
{
|
||||
@ -146,14 +160,17 @@ namespace Emby.Notifications
|
||||
|
||||
lock (_libraryChangedSyncLock)
|
||||
{
|
||||
if (LibraryUpdateTimer == null)
|
||||
if (_libraryUpdateTimer == null)
|
||||
{
|
||||
LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, 5000,
|
||||
Timeout.Infinite);
|
||||
_libraryUpdateTimer = new Timer(
|
||||
LibraryUpdateTimerCallback,
|
||||
null,
|
||||
5000,
|
||||
Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
{
|
||||
LibraryUpdateTimer.Change(5000, Timeout.Infinite);
|
||||
_libraryUpdateTimer.Change(5000, Timeout.Infinite);
|
||||
}
|
||||
|
||||
_itemsAdded.Add(e.Item);
|
||||
@ -188,7 +205,8 @@ namespace Emby.Notifications
|
||||
{
|
||||
items = _itemsAdded.ToList();
|
||||
_itemsAdded.Clear();
|
||||
DisposeLibraryUpdateTimer();
|
||||
_libraryUpdateTimer!.Dispose(); // Shouldn't be null as it just set off this callback
|
||||
_libraryUpdateTimer = null;
|
||||
}
|
||||
|
||||
items = items.Take(10).ToList();
|
||||
@ -198,7 +216,10 @@ namespace Emby.Notifications
|
||||
var notification = new NotificationRequest
|
||||
{
|
||||
NotificationType = NotificationType.NewLibraryContent.ToString(),
|
||||
Name = string.Format(_localization.GetLocalizedString("ValueHasBeenAddedToLibrary"), GetItemName(item)),
|
||||
Name = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("ValueHasBeenAddedToLibrary"),
|
||||
GetItemName(item)),
|
||||
Description = item.Overview
|
||||
};
|
||||
|
||||
@ -206,6 +227,11 @@ namespace Emby.Notifications
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a human readable name for the item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>A human readable name for the item.</returns>
|
||||
public static string GetItemName(BaseItem item)
|
||||
{
|
||||
var name = item.Name;
|
||||
@ -219,6 +245,7 @@ namespace Emby.Notifications
|
||||
episode.IndexNumber.Value,
|
||||
name);
|
||||
}
|
||||
|
||||
if (episode.ParentIndexNumber.HasValue)
|
||||
{
|
||||
name = string.Format(
|
||||
@ -229,7 +256,6 @@ namespace Emby.Notifications
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (item is IHasSeries hasSeries)
|
||||
{
|
||||
name = hasSeries.SeriesName + " - " + name;
|
||||
@ -257,7 +283,7 @@ namespace Emby.Notifications
|
||||
return name;
|
||||
}
|
||||
|
||||
private async Task SendNotification(NotificationRequest notification, BaseItem relatedItem)
|
||||
private async Task SendNotification(NotificationRequest notification, BaseItem? relatedItem)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -269,23 +295,37 @@ namespace Emby.Notifications
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeLibraryUpdateTimer();
|
||||
|
||||
_libraryManager.ItemAdded -= _libraryManager_ItemAdded;
|
||||
_appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged;
|
||||
_appHost.HasUpdateAvailableChanged -= _appHost_HasUpdateAvailableChanged;
|
||||
_activityManager.EntryCreated -= _activityManager_EntryCreated;
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void DisposeLibraryUpdateTimer()
|
||||
/// <summary>
|
||||
/// Releases unmanaged and optionally managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (LibraryUpdateTimer != null)
|
||||
if (_disposed)
|
||||
{
|
||||
LibraryUpdateTimer.Dispose();
|
||||
LibraryUpdateTimer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_libraryUpdateTimer?.Dispose();
|
||||
}
|
||||
|
||||
_libraryUpdateTimer = null;
|
||||
|
||||
_libraryManager.ItemAdded -= OnLibraryManagerItemAdded;
|
||||
_appHost.HasPendingRestartChanged -= OnAppHostHasPendingRestartChanged;
|
||||
_appHost.HasUpdateAvailableChanged -= OnAppHostHasUpdateAvailableChanged;
|
||||
_activityManager.EntryCreated -= OnActivityManagerEntryCreated;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -16,20 +16,32 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// NotificationManager class.
|
||||
/// </summary>
|
||||
public class NotificationManager : INotificationManager
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
private INotificationService[] _services;
|
||||
private INotificationTypeFactory[] _typeFactories;
|
||||
private INotificationService[] _services = Array.Empty<INotificationService>();
|
||||
private INotificationTypeFactory[] _typeFactories = Array.Empty<INotificationTypeFactory>();
|
||||
|
||||
public NotificationManager(ILoggerFactory loggerFactory, IUserManager userManager, IServerConfigurationManager config)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NotificationManager" /> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="userManager">The user manager.</param>
|
||||
/// <param name="config">The server configuration manager.</param>
|
||||
public NotificationManager(
|
||||
ILogger<NotificationManager> logger,
|
||||
IUserManager userManager,
|
||||
IServerConfigurationManager config)
|
||||
{
|
||||
_logger = logger;
|
||||
_userManager = userManager;
|
||||
_config = config;
|
||||
_logger = loggerFactory.CreateLogger(GetType().Name);
|
||||
}
|
||||
|
||||
private NotificationOptions GetConfiguration()
|
||||
@ -37,12 +49,14 @@ namespace Emby.Notifications
|
||||
return _config.GetConfiguration<NotificationOptions>("notifications");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendNotification(NotificationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return SendNotification(request, null, cancellationToken);
|
||||
}
|
||||
|
||||
public Task SendNotification(NotificationRequest request, BaseItem relatedItem, CancellationToken cancellationToken)
|
||||
/// <inheritdoc />
|
||||
public Task SendNotification(NotificationRequest request, BaseItem? relatedItem, CancellationToken cancellationToken)
|
||||
{
|
||||
var notificationType = request.NotificationType;
|
||||
|
||||
@ -64,7 +78,8 @@ namespace Emby.Notifications
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private Task SendNotification(NotificationRequest request,
|
||||
private Task SendNotification(
|
||||
NotificationRequest request,
|
||||
INotificationService service,
|
||||
IEnumerable<User> users,
|
||||
string title,
|
||||
@ -79,7 +94,7 @@ namespace Emby.Notifications
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private IEnumerable<Guid> GetUserIds(NotificationRequest request, NotificationOption options)
|
||||
private IEnumerable<Guid> GetUserIds(NotificationRequest request, NotificationOption? options)
|
||||
{
|
||||
if (request.SendToUserMode.HasValue)
|
||||
{
|
||||
@ -109,7 +124,8 @@ namespace Emby.Notifications
|
||||
return request.UserIds;
|
||||
}
|
||||
|
||||
private async Task SendNotification(NotificationRequest request,
|
||||
private async Task SendNotification(
|
||||
NotificationRequest request,
|
||||
INotificationService service,
|
||||
string title,
|
||||
string description,
|
||||
@ -161,12 +177,14 @@ namespace Emby.Notifications
|
||||
return GetConfiguration().IsServiceEnabled(service.Name, notificationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddParts(IEnumerable<INotificationService> services, IEnumerable<INotificationTypeFactory> notificationTypeFactories)
|
||||
{
|
||||
_services = services.ToArray();
|
||||
_typeFactories = notificationTypeFactories.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<NotificationTypeInfo> GetNotificationTypes()
|
||||
{
|
||||
var list = _typeFactories.Select(i =>
|
||||
@ -180,7 +198,6 @@ namespace Emby.Notifications
|
||||
_logger.LogError(ex, "Error in GetNotificationTypes");
|
||||
return new List<NotificationTypeInfo>();
|
||||
}
|
||||
|
||||
}).SelectMany(i => i).ToList();
|
||||
|
||||
var config = GetConfiguration();
|
||||
@ -193,13 +210,13 @@ namespace Emby.Notifications
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<NameIdPair> GetNotificationServices()
|
||||
{
|
||||
return _services.Select(i => new NameIdPair
|
||||
{
|
||||
Name = i.Name,
|
||||
Id = i.Name.GetMD5().ToString("N", CultureInfo.InvariantCulture)
|
||||
|
||||
}).OrderBy(i => i.Name);
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
|
@ -29,7 +29,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Activity
|
||||
{
|
||||
public class ActivityLogEntryPoint : IServerEntryPoint
|
||||
public sealed class ActivityLogEntryPoint : IServerEntryPoint
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IInstallationManager _installationManager;
|
||||
@ -39,7 +39,6 @@ namespace Emby.Server.Implementations.Activity
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly ISubtitleManager _subManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IDeviceManager _deviceManager;
|
||||
|
||||
/// <summary>
|
||||
@ -64,8 +63,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
ILocalizationManager localization,
|
||||
IInstallationManager installationManager,
|
||||
ISubtitleManager subManager,
|
||||
IUserManager userManager,
|
||||
IServerApplicationHost appHost)
|
||||
IUserManager userManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_sessionManager = sessionManager;
|
||||
@ -76,7 +74,6 @@ namespace Emby.Server.Implementations.Activity
|
||||
_installationManager = installationManager;
|
||||
_subManager = subManager;
|
||||
_userManager = userManager;
|
||||
_appHost = appHost;
|
||||
}
|
||||
|
||||
public Task RunAsync()
|
||||
@ -141,7 +138,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
|
||||
e.Provider,
|
||||
Notifications.Notifications.GetItemName(e.Item)),
|
||||
Emby.Notifications.NotificationEntryPoint.GetItemName(e.Item)),
|
||||
Type = "SubtitleDownloadFailure",
|
||||
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||
ShortOverview = e.Exception.Message
|
||||
@ -533,6 +530,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
private void CreateLogEntry(ActivityLogEntry entry)
|
||||
=> _activityManager.Create(entry);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_taskManager.TaskCompleted -= OnTaskCompleted;
|
||||
|
@ -819,7 +819,18 @@ namespace Emby.Server.Implementations
|
||||
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager);
|
||||
serviceCollection.AddSingleton(ChannelManager);
|
||||
|
||||
SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, this, AuthenticationRepository, DeviceManager, MediaSourceManager);
|
||||
SessionManager = new SessionManager(
|
||||
LoggerFactory.CreateLogger<SessionManager>(),
|
||||
UserDataManager,
|
||||
LibraryManager,
|
||||
UserManager,
|
||||
musicManager,
|
||||
DtoService,
|
||||
ImageProcessor,
|
||||
this,
|
||||
AuthenticationRepository,
|
||||
DeviceManager,
|
||||
MediaSourceManager);
|
||||
serviceCollection.AddSingleton(SessionManager);
|
||||
|
||||
serviceCollection.AddSingleton<IDlnaManager>(
|
||||
@ -836,7 +847,10 @@ namespace Emby.Server.Implementations
|
||||
UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
|
||||
serviceCollection.AddSingleton(UserViewManager);
|
||||
|
||||
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
|
||||
NotificationManager = new NotificationManager(
|
||||
LoggerFactory.CreateLogger<NotificationManager>(),
|
||||
UserManager,
|
||||
ServerConfigurationManager);
|
||||
serviceCollection.AddSingleton(NotificationManager);
|
||||
|
||||
serviceCollection.AddSingleton<IDeviceDiscovery>(new DeviceDiscovery(ServerConfigurationManager));
|
||||
@ -1074,8 +1088,6 @@ namespace Emby.Server.Implementations
|
||||
GetExports<IMetadataSaver>(),
|
||||
GetExports<IExternalId>());
|
||||
|
||||
ImageProcessor.ImageEnhancers = GetExports<IImageEnhancer>();
|
||||
|
||||
LiveTvManager.AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
|
||||
|
||||
SubtitleManager.AddParts(GetExports<ISubtitleProvider>());
|
||||
|
@ -3521,20 +3521,6 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
|
||||
var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
|
||||
if (includeTypes.Length == 1)
|
||||
{
|
||||
whereClauses.Add("type=@type");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@type", includeTypes[0]);
|
||||
}
|
||||
}
|
||||
else if (includeTypes.Length > 1)
|
||||
{
|
||||
var inClause = string.Join(",", includeTypes.Select(i => "'" + i + "'"));
|
||||
whereClauses.Add($"type in ({inClause})");
|
||||
}
|
||||
|
||||
// Only specify excluded types if no included types are specified
|
||||
if (includeTypes.Length == 0)
|
||||
{
|
||||
@ -3553,6 +3539,19 @@ namespace Emby.Server.Implementations.Data
|
||||
whereClauses.Add($"type not in ({inClause})");
|
||||
}
|
||||
}
|
||||
else if (includeTypes.Length == 1)
|
||||
{
|
||||
whereClauses.Add("type=@type");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@type", includeTypes[0]);
|
||||
}
|
||||
}
|
||||
else if (includeTypes.Length > 1)
|
||||
{
|
||||
var inClause = string.Join(",", includeTypes.Select(i => "'" + i + "'"));
|
||||
whereClauses.Add($"type in ({inClause})");
|
||||
}
|
||||
|
||||
if (query.ChannelIds.Length == 1)
|
||||
{
|
||||
@ -4927,7 +4926,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
// Not crazy about having this all the way down here, but at least it's in one place
|
||||
readonly Dictionary<string, string[]> _types = GetTypeMapDictionary();
|
||||
|
||||
private IEnumerable<string> MapIncludeItemTypes(string value)
|
||||
private string[] MapIncludeItemTypes(string value)
|
||||
{
|
||||
if (_types.TryGetValue(value, out string[] result))
|
||||
{
|
||||
@ -5611,32 +5610,32 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
return counts;
|
||||
}
|
||||
|
||||
private List<Tuple<int, string>> GetItemValuesToSave(BaseItem item, List<string> inheritedTags)
|
||||
private List<(int, string)> GetItemValuesToSave(BaseItem item, List<string> inheritedTags)
|
||||
{
|
||||
var list = new List<Tuple<int, string>>();
|
||||
var list = new List<(int, string)>();
|
||||
|
||||
if (item is IHasArtist hasArtist)
|
||||
{
|
||||
list.AddRange(hasArtist.Artists.Select(i => new Tuple<int, string>(0, i)));
|
||||
list.AddRange(hasArtist.Artists.Select(i => (0, i)));
|
||||
}
|
||||
|
||||
if (item is IHasAlbumArtist hasAlbumArtist)
|
||||
{
|
||||
list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => new Tuple<int, string>(1, i)));
|
||||
list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => (1, i)));
|
||||
}
|
||||
|
||||
list.AddRange(item.Genres.Select(i => new Tuple<int, string>(2, i)));
|
||||
list.AddRange(item.Studios.Select(i => new Tuple<int, string>(3, i)));
|
||||
list.AddRange(item.Tags.Select(i => new Tuple<int, string>(4, i)));
|
||||
list.AddRange(item.Genres.Select(i => (2, i)));
|
||||
list.AddRange(item.Studios.Select(i => (3, i)));
|
||||
list.AddRange(item.Tags.Select(i => (4, i)));
|
||||
|
||||
// keywords was 5
|
||||
|
||||
list.AddRange(inheritedTags.Select(i => new Tuple<int, string>(6, i)));
|
||||
list.AddRange(inheritedTags.Select(i => (6, i)));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private void UpdateItemValues(Guid itemId, List<Tuple<int, string>> values, IDatabaseConnection db)
|
||||
private void UpdateItemValues(Guid itemId, List<(int, string)> values, IDatabaseConnection db)
|
||||
{
|
||||
if (itemId.Equals(Guid.Empty))
|
||||
{
|
||||
@ -5658,7 +5657,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
InsertItemValues(guidBlob, values, db);
|
||||
}
|
||||
|
||||
private void InsertItemValues(byte[] idBlob, List<Tuple<int, string>> values, IDatabaseConnection db)
|
||||
private void InsertItemValues(byte[] idBlob, List<(int, string)> values, IDatabaseConnection db)
|
||||
{
|
||||
var startIndex = 0;
|
||||
var limit = 100;
|
||||
|
@ -142,11 +142,10 @@ namespace Emby.Server.Implementations.Devices
|
||||
|
||||
public QueryResult<DeviceInfo> GetDevices(DeviceQuery query)
|
||||
{
|
||||
var sessions = _authRepo.Get(new AuthenticationInfoQuery
|
||||
IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery
|
||||
{
|
||||
//UserId = query.UserId
|
||||
HasUser = true
|
||||
|
||||
}).Items;
|
||||
|
||||
// TODO: DeviceQuery doesn't seem to be used from client. Not even Swagger.
|
||||
@ -154,23 +153,19 @@ namespace Emby.Server.Implementations.Devices
|
||||
{
|
||||
var val = query.SupportsSync.Value;
|
||||
|
||||
sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val).ToArray();
|
||||
sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val);
|
||||
}
|
||||
|
||||
if (!query.UserId.Equals(Guid.Empty))
|
||||
{
|
||||
var user = _userManager.GetUserById(query.UserId);
|
||||
|
||||
sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)).ToArray();
|
||||
sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId));
|
||||
}
|
||||
|
||||
var array = sessions.Select(ToDeviceInfo).ToArray();
|
||||
|
||||
return new QueryResult<DeviceInfo>
|
||||
{
|
||||
Items = array,
|
||||
TotalRecordCount = array.Length
|
||||
};
|
||||
return new QueryResult<DeviceInfo>(array);
|
||||
}
|
||||
|
||||
private DeviceInfo ToDeviceInfo(AuthenticationInfo authInfo)
|
||||
@ -186,7 +181,7 @@ namespace Emby.Server.Implementations.Devices
|
||||
LastUserName = authInfo.UserName,
|
||||
Name = authInfo.DeviceName,
|
||||
DateLastActivity = authInfo.DateLastActivity,
|
||||
IconUrl = caps == null ? null : caps.IconUrl
|
||||
IconUrl = caps?.IconUrl
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1362,56 +1362,33 @@ namespace Emby.Server.Implementations.Dto
|
||||
return null;
|
||||
}
|
||||
|
||||
var supportedEnhancers = _imageProcessor.GetSupportedEnhancers(item, ImageType.Primary).ToArray();
|
||||
|
||||
ImageDimensions size;
|
||||
|
||||
var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
|
||||
|
||||
if (defaultAspectRatio > 0)
|
||||
{
|
||||
if (supportedEnhancers.Length == 0)
|
||||
{
|
||||
return defaultAspectRatio;
|
||||
}
|
||||
|
||||
int dummyWidth = 200;
|
||||
int dummyHeight = Convert.ToInt32(dummyWidth / defaultAspectRatio);
|
||||
size = new ImageDimensions(dummyWidth, dummyHeight);
|
||||
return defaultAspectRatio;
|
||||
}
|
||||
else
|
||||
|
||||
if (!imageInfo.IsLocalFile)
|
||||
{
|
||||
if (!imageInfo.IsLocalFile)
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
size = _imageProcessor.GetImageDimensions(item, imageInfo);
|
||||
|
||||
if (size.Width <= 0 || size.Height <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
size = _imageProcessor.GetImageDimensions(item, imageInfo);
|
||||
|
||||
if (size.Width <= 0 || size.Height <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var enhancer in supportedEnhancers)
|
||||
catch (Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
size = enhancer.GetEnhancedImageSize(item, ImageType.Primary, 0, size);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in image enhancer: {0}", enhancer.GetType().Name);
|
||||
}
|
||||
_logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path);
|
||||
return null;
|
||||
}
|
||||
|
||||
var width = size.Width;
|
||||
|
@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
public class RecordingNotifier : IServerEntryPoint
|
||||
public sealed class RecordingNotifier : IServerEntryPoint
|
||||
{
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
@ -28,32 +28,33 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
_liveTvManager = liveTvManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RunAsync()
|
||||
{
|
||||
_liveTvManager.TimerCancelled += _liveTvManager_TimerCancelled;
|
||||
_liveTvManager.SeriesTimerCancelled += _liveTvManager_SeriesTimerCancelled;
|
||||
_liveTvManager.TimerCreated += _liveTvManager_TimerCreated;
|
||||
_liveTvManager.SeriesTimerCreated += _liveTvManager_SeriesTimerCreated;
|
||||
_liveTvManager.TimerCancelled += OnLiveTvManagerTimerCancelled;
|
||||
_liveTvManager.SeriesTimerCancelled += OnLiveTvManagerSeriesTimerCancelled;
|
||||
_liveTvManager.TimerCreated += OnLiveTvManagerTimerCreated;
|
||||
_liveTvManager.SeriesTimerCreated += OnLiveTvManagerSeriesTimerCreated;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void _liveTvManager_SeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
private void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
{
|
||||
SendMessage("SeriesTimerCreated", e.Argument);
|
||||
}
|
||||
|
||||
private void _liveTvManager_TimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
private void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
{
|
||||
SendMessage("TimerCreated", e.Argument);
|
||||
}
|
||||
|
||||
private void _liveTvManager_SeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
private void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
{
|
||||
SendMessage("SeriesTimerCancelled", e.Argument);
|
||||
}
|
||||
|
||||
private void _liveTvManager_TimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
private void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
|
||||
{
|
||||
SendMessage("TimerCancelled", e.Argument);
|
||||
}
|
||||
@ -64,11 +65,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
|
||||
try
|
||||
{
|
||||
await _sessionManager.SendMessageToUserSessions(users, name, info, CancellationToken.None);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// TODO Log exception or Investigate and properly fix.
|
||||
await _sessionManager.SendMessageToUserSessions(users, name, info, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -76,12 +73,13 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_liveTvManager.TimerCancelled -= _liveTvManager_TimerCancelled;
|
||||
_liveTvManager.SeriesTimerCancelled -= _liveTvManager_SeriesTimerCancelled;
|
||||
_liveTvManager.TimerCreated -= _liveTvManager_TimerCreated;
|
||||
_liveTvManager.SeriesTimerCreated -= _liveTvManager_SeriesTimerCreated;
|
||||
_liveTvManager.TimerCancelled -= OnLiveTvManagerTimerCancelled;
|
||||
_liveTvManager.SeriesTimerCancelled -= OnLiveTvManagerSeriesTimerCancelled;
|
||||
_liveTvManager.TimerCreated -= OnLiveTvManagerTimerCreated;
|
||||
_liveTvManager.SeriesTimerCreated -= OnLiveTvManagerSeriesTimerCreated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
@ -15,21 +14,17 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
/// </summary>
|
||||
public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// The user manager.
|
||||
/// </summary>
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
private IFileSystem _fileSystem;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RefreshUsersMetadata" /> class.
|
||||
/// </summary>
|
||||
public RefreshUsersMetadata(ILogger logger, IUserManager userManager, IFileSystem fileSystem)
|
||||
public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem)
|
||||
{
|
||||
_logger = logger;
|
||||
_userManager = userManager;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
@ -3,37 +3,28 @@ using Emby.Server.Implementations.Browser;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
/// <summary>
|
||||
/// Class StartupWizard.
|
||||
/// </summary>
|
||||
public class StartupWizard : IServerEntryPoint
|
||||
public sealed class StartupWizard : IServerEntryPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The app host.
|
||||
/// </summary>
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
|
||||
/// <summary>
|
||||
/// The user manager.
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private IServerConfigurationManager _config;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StartupWizard"/> class.
|
||||
/// </summary>
|
||||
/// <param name="appHost">The application host.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="config">The configuration manager.</param>
|
||||
public StartupWizard(IServerApplicationHost appHost, ILogger logger, IServerConfigurationManager config)
|
||||
public StartupWizard(IServerApplicationHost appHost, IServerConfigurationManager config)
|
||||
{
|
||||
_appHost = appHost;
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,6 @@ using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Udp;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
@ -23,9 +21,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
/// The logger.
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
private readonly ISocketFactory _socketFactory;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IJsonSerializer _json;
|
||||
|
||||
/// <summary>
|
||||
/// The UDP server.
|
||||
@ -64,7 +60,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
|
||||
_cancellationTokenSource.Cancel();
|
||||
_udpServer.Dispose();
|
||||
|
||||
_cancellationTokenSource.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
_udpServer = null;
|
||||
|
||||
|
@ -13,39 +13,38 @@ using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
public class UserDataChangeNotifier : IServerEntryPoint
|
||||
public sealed class UserDataChangeNotifier : IServerEntryPoint
|
||||
{
|
||||
private const int UpdateDuration = 500;
|
||||
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
private readonly object _syncLock = new object();
|
||||
private Timer UpdateTimer { get; set; }
|
||||
private const int UpdateDuration = 500;
|
||||
|
||||
private readonly Dictionary<Guid, List<BaseItem>> _changedItems = new Dictionary<Guid, List<BaseItem>>();
|
||||
|
||||
public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager)
|
||||
private readonly object _syncLock = new object();
|
||||
private Timer _updateTimer;
|
||||
|
||||
|
||||
public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, IUserManager userManager)
|
||||
{
|
||||
_userDataManager = userDataManager;
|
||||
_sessionManager = sessionManager;
|
||||
_logger = logger;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
public Task RunAsync()
|
||||
{
|
||||
_userDataManager.UserDataSaved += _userDataManager_UserDataSaved;
|
||||
_userDataManager.UserDataSaved += OnUserDataManagerUserDataSaved;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
void _userDataManager_UserDataSaved(object sender, UserDataSaveEventArgs e)
|
||||
void OnUserDataManagerUserDataSaved(object sender, UserDataSaveEventArgs e)
|
||||
{
|
||||
if (e.SaveReason == UserDataSaveReason.PlaybackProgress)
|
||||
{
|
||||
@ -54,14 +53,17 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (UpdateTimer == null)
|
||||
if (_updateTimer == null)
|
||||
{
|
||||
UpdateTimer = new Timer(UpdateTimerCallback, null, UpdateDuration,
|
||||
Timeout.Infinite);
|
||||
_updateTimer = new Timer(
|
||||
UpdateTimerCallback,
|
||||
null,
|
||||
UpdateDuration,
|
||||
Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateTimer.Change(UpdateDuration, Timeout.Infinite);
|
||||
_updateTimer.Change(UpdateDuration, Timeout.Infinite);
|
||||
}
|
||||
|
||||
if (!_changedItems.TryGetValue(e.UserId, out List<BaseItem> keys))
|
||||
@ -97,10 +99,10 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
|
||||
var task = SendNotifications(changes, CancellationToken.None);
|
||||
|
||||
if (UpdateTimer != null)
|
||||
if (_updateTimer != null)
|
||||
{
|
||||
UpdateTimer.Dispose();
|
||||
UpdateTimer = null;
|
||||
_updateTimer.Dispose();
|
||||
_updateTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -145,13 +147,13 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (UpdateTimer != null)
|
||||
if (_updateTimer != null)
|
||||
{
|
||||
UpdateTimer.Dispose();
|
||||
UpdateTimer = null;
|
||||
_updateTimer.Dispose();
|
||||
_updateTimer = null;
|
||||
}
|
||||
|
||||
_userDataManager.UserDataSaved -= _userDataManager_UserDataSaved;
|
||||
_userDataManager.UserDataSaved -= OnUserDataManagerUserDataSaved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||
if (!string.IsNullOrWhiteSpace(userInfo))
|
||||
{
|
||||
_logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url);
|
||||
url = url.Replace(userInfo + '@', string.Empty);
|
||||
url = url.Replace(userInfo + '@', string.Empty, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(method, url);
|
||||
|
@ -40,9 +40,9 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
private readonly Func<Type, Func<string, object>> _funcParseFn;
|
||||
private readonly string _defaultRedirectPath;
|
||||
private readonly string _baseUrlPrefix;
|
||||
private readonly Dictionary<Type, Type> ServiceOperationsMap = new Dictionary<Type, Type>();
|
||||
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
|
||||
private readonly Dictionary<Type, Type> _serviceOperationsMap = new Dictionary<Type, Type>();
|
||||
private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>();
|
||||
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
|
||||
private bool _disposed = false;
|
||||
|
||||
public HttpListenerHost(
|
||||
@ -72,6 +72,8 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
ResponseFilters = Array.Empty<Action<IRequest, HttpResponse, object>>();
|
||||
}
|
||||
|
||||
public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
|
||||
|
||||
public Action<IRequest, HttpResponse, object>[] ResponseFilters { get; set; }
|
||||
|
||||
public static HttpListenerHost Instance { get; protected set; }
|
||||
@ -82,8 +84,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
public ServiceController ServiceController { get; private set; }
|
||||
|
||||
public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
|
||||
|
||||
public object CreateInstance(Type type)
|
||||
{
|
||||
return _appHost.CreateInstance(type);
|
||||
@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
private static string NormalizeUrlPath(string path)
|
||||
{
|
||||
if (path.StartsWith("/"))
|
||||
if (path.Length > 0 && path[0] == '/')
|
||||
{
|
||||
// If the path begins with a leading slash, just return it as-is
|
||||
return path;
|
||||
@ -131,13 +131,13 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
public Type GetServiceTypeByRequest(Type requestType)
|
||||
{
|
||||
ServiceOperationsMap.TryGetValue(requestType, out var serviceType);
|
||||
_serviceOperationsMap.TryGetValue(requestType, out var serviceType);
|
||||
return serviceType;
|
||||
}
|
||||
|
||||
public void AddServiceInfo(Type serviceType, Type requestType)
|
||||
{
|
||||
ServiceOperationsMap[requestType] = serviceType;
|
||||
_serviceOperationsMap[requestType] = serviceType;
|
||||
}
|
||||
|
||||
private List<IHasRequestFilter> GetRequestFilterAttributes(Type requestDtoType)
|
||||
@ -199,7 +199,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
else
|
||||
{
|
||||
var inners = agg.InnerExceptions;
|
||||
if (inners != null && inners.Count > 0)
|
||||
if (inners.Count > 0)
|
||||
{
|
||||
return GetActualException(inners[0]);
|
||||
}
|
||||
@ -362,7 +362,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
return true;
|
||||
}
|
||||
|
||||
host = host ?? string.Empty;
|
||||
host ??= string.Empty;
|
||||
|
||||
if (_networkManager.IsInPrivateAddressSpace(host))
|
||||
{
|
||||
@ -433,7 +433,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overridable method that can be used to implement a custom hnandler
|
||||
/// Overridable method that can be used to implement a custom handler.
|
||||
/// </summary>
|
||||
public async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
|
||||
{
|
||||
@ -492,7 +492,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|| string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.IsNullOrEmpty(localPath)
|
||||
|| !localPath.StartsWith(_baseUrlPrefix))
|
||||
|| !localPath.StartsWith(_baseUrlPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Always redirect back to the default path if the base prefix is invalid or missing
|
||||
_logger.LogDebug("Normalizing a URL at {0}", localPath);
|
||||
@ -693,7 +693,10 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
|
@ -6,7 +6,9 @@ namespace Emby.Server.Implementations.IO
|
||||
public class ExtendedFileSystemInfo
|
||||
{
|
||||
public bool IsHidden { get; set; }
|
||||
|
||||
public bool IsReadOnly { get; set; }
|
||||
|
||||
public bool Exists { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -15,27 +15,29 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
public class FileRefresher : IDisposable
|
||||
{
|
||||
private ILogger Logger { get; set; }
|
||||
private ILibraryManager LibraryManager { get; set; }
|
||||
private IServerConfigurationManager ConfigurationManager { get; set; }
|
||||
private readonly List<string> _affectedPaths = new List<string>();
|
||||
private Timer _timer;
|
||||
private readonly object _timerLock = new object();
|
||||
public string Path { get; private set; }
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
|
||||
public event EventHandler<EventArgs> Completed;
|
||||
private readonly List<string> _affectedPaths = new List<string>();
|
||||
private readonly object _timerLock = new object();
|
||||
private Timer _timer;
|
||||
|
||||
public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
|
||||
{
|
||||
logger.LogDebug("New file refresher created for {0}", path);
|
||||
Path = path;
|
||||
|
||||
ConfigurationManager = configurationManager;
|
||||
LibraryManager = libraryManager;
|
||||
Logger = logger;
|
||||
_configurationManager = configurationManager;
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
AddPath(path);
|
||||
}
|
||||
|
||||
public event EventHandler<EventArgs> Completed;
|
||||
|
||||
public string Path { get; private set; }
|
||||
|
||||
private void AddAffectedPath(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
@ -80,11 +82,11 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
if (_timer == null)
|
||||
{
|
||||
_timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
|
||||
_timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
|
||||
}
|
||||
else
|
||||
{
|
||||
_timer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
|
||||
_timer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,7 +95,7 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
lock (_timerLock)
|
||||
{
|
||||
Logger.LogDebug("Resetting file refresher from {0} to {1}", Path, path);
|
||||
_logger.LogDebug("Resetting file refresher from {0} to {1}", Path, path);
|
||||
|
||||
Path = path;
|
||||
AddAffectedPath(path);
|
||||
@ -116,7 +118,7 @@ namespace Emby.Server.Implementations.IO
|
||||
paths = _affectedPaths.ToList();
|
||||
}
|
||||
|
||||
Logger.LogDebug("Timer stopped.");
|
||||
_logger.LogDebug("Timer stopped.");
|
||||
|
||||
DisposeTimer();
|
||||
Completed?.Invoke(this, EventArgs.Empty);
|
||||
@ -127,7 +129,7 @@ namespace Emby.Server.Implementations.IO
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error processing directory changes");
|
||||
_logger.LogError(ex, "Error processing directory changes");
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,7 +149,7 @@ namespace Emby.Server.Implementations.IO
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.LogInformation("{name} ({path}) will be refreshed.", item.Name, item.Path);
|
||||
_logger.LogInformation("{name} ({path}) will be refreshed.", item.Name, item.Path);
|
||||
|
||||
try
|
||||
{
|
||||
@ -158,11 +160,11 @@ namespace Emby.Server.Implementations.IO
|
||||
// For now swallow and log.
|
||||
// Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable)
|
||||
// Should we remove it from it's parent?
|
||||
Logger.LogError(ex, "Error refreshing {name}", item.Name);
|
||||
_logger.LogError(ex, "Error refreshing {name}", item.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error refreshing {name}", item.Name);
|
||||
_logger.LogError(ex, "Error refreshing {name}", item.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -178,7 +180,7 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
while (item == null && !string.IsNullOrEmpty(path))
|
||||
{
|
||||
item = LibraryManager.FindByPath(path, null);
|
||||
item = _libraryManager.FindByPath(path, null);
|
||||
|
||||
path = System.IO.Path.GetDirectoryName(path);
|
||||
}
|
||||
|
@ -1174,7 +1174,6 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
return _fileSystem.GetDirectoryPaths(ConfigurationManager.ApplicationPaths.DefaultUserViewsPath)
|
||||
.Select(dir => GetVirtualFolderInfo(dir, topLibraryFolders, refreshQueue))
|
||||
.OrderBy(i => i.Name)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@ -1406,25 +1405,32 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
private void SetTopParentOrAncestorIds(InternalItemsQuery query)
|
||||
{
|
||||
if (query.AncestorIds.Length == 0)
|
||||
var ancestorIds = query.AncestorIds;
|
||||
int len = ancestorIds.Length;
|
||||
if (len == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var parents = query.AncestorIds.Select(i => GetItemById(i)).ToList();
|
||||
|
||||
if (parents.All(i => i is ICollectionFolder || i is UserView))
|
||||
var parents = new BaseItem[len];
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
// Optimize by querying against top level views
|
||||
query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray();
|
||||
query.AncestorIds = Array.Empty<Guid>();
|
||||
|
||||
// Prevent searching in all libraries due to empty filter
|
||||
if (query.TopParentIds.Length == 0)
|
||||
parents[i] = GetItemById(ancestorIds[i]);
|
||||
if (!(parents[i] is ICollectionFolder || parents[i] is UserView))
|
||||
{
|
||||
query.TopParentIds = new[] { Guid.NewGuid() };
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Optimize by querying against top level views
|
||||
query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray();
|
||||
query.AncestorIds = Array.Empty<Guid>();
|
||||
|
||||
// Prevent searching in all libraries due to empty filter
|
||||
if (query.TopParentIds.Length == 0)
|
||||
{
|
||||
query.TopParentIds = new[] { Guid.NewGuid() };
|
||||
}
|
||||
}
|
||||
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
|
||||
@ -1585,7 +1591,7 @@ namespace Emby.Server.Implementations.Library
|
||||
public async Task<IEnumerable<Video>> GetIntros(BaseItem item, User user)
|
||||
{
|
||||
var tasks = IntroProviders
|
||||
.OrderBy(i => i.GetType().Name.IndexOf("Default", StringComparison.OrdinalIgnoreCase) == -1 ? 0 : 1)
|
||||
.OrderBy(i => i.GetType().Name.Contains("Default", StringComparison.OrdinalIgnoreCase) ? 1 : 0)
|
||||
.Take(1)
|
||||
.Select(i => GetIntros(i, item, user));
|
||||
|
||||
@ -2363,33 +2369,22 @@ namespace Emby.Server.Implementations.Library
|
||||
new SubtitleResolver(BaseItem.LocalizationManager, _fileSystem).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files);
|
||||
}
|
||||
|
||||
public bool IsVideoFile(string path, LibraryOptions libraryOptions)
|
||||
/// <inheritdoc />
|
||||
public bool IsVideoFile(string path)
|
||||
{
|
||||
var resolver = new VideoResolver(GetNamingOptions());
|
||||
return resolver.IsVideoFile(path);
|
||||
}
|
||||
|
||||
public bool IsVideoFile(string path)
|
||||
{
|
||||
return IsVideoFile(path, new LibraryOptions());
|
||||
}
|
||||
|
||||
public bool IsAudioFile(string path, LibraryOptions libraryOptions)
|
||||
{
|
||||
var parser = new AudioFileParser(GetNamingOptions());
|
||||
return parser.IsAudioFile(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsAudioFile(string path)
|
||||
{
|
||||
return IsAudioFile(path, new LibraryOptions());
|
||||
}
|
||||
=> AudioFileParser.IsAudioFile(path, GetNamingOptions());
|
||||
|
||||
/// <inheritdoc />
|
||||
public int? GetSeasonNumberFromPath(string path)
|
||||
{
|
||||
return SeasonPathParser.Parse(path, true, true).SeasonNumber;
|
||||
}
|
||||
=> SeasonPathParser.Parse(path, true, true).SeasonNumber;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
|
||||
{
|
||||
var series = episode.Series;
|
||||
|
@ -73,7 +73,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
{
|
||||
// Return audio if the path is a file and has a matching extension
|
||||
|
||||
var libraryOptions = args.GetLibraryOptions();
|
||||
var collectionType = args.GetCollectionType();
|
||||
|
||||
var isBooksCollectionType = string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase);
|
||||
@ -92,7 +91,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
|
||||
}
|
||||
|
||||
if (LibraryManager.IsAudioFile(args.Path, libraryOptions))
|
||||
if (LibraryManager.IsAudioFile(args.Path))
|
||||
{
|
||||
var extension = Path.GetExtension(args.Path);
|
||||
|
||||
@ -105,7 +104,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
var isMixedCollectionType = string.IsNullOrEmpty(collectionType);
|
||||
|
||||
// For conflicting extensions, give priority to videos
|
||||
if (isMixedCollectionType && LibraryManager.IsVideoFile(args.Path, libraryOptions))
|
||||
if (isMixedCollectionType && LibraryManager.IsVideoFile(args.Path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -121,7 +120,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
{
|
||||
item = new MediaBrowser.Controller.Entities.Audio.Audio();
|
||||
}
|
||||
|
||||
else if (isBooksCollectionType)
|
||||
{
|
||||
item = new AudioBook();
|
||||
|
@ -5,7 +5,6 @@ using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -78,9 +77,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
/// <summary>
|
||||
/// Determine if the supplied file data points to a music album.
|
||||
/// </summary>
|
||||
public bool IsMusicAlbum(string path, IDirectoryService directoryService, LibraryOptions libraryOptions)
|
||||
public bool IsMusicAlbum(string path, IDirectoryService directoryService)
|
||||
{
|
||||
return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, libraryOptions, _libraryManager);
|
||||
return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, _libraryManager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -94,7 +93,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
if (args.IsDirectory)
|
||||
{
|
||||
// if (args.Parent is MusicArtist) return true; //saves us from testing children twice
|
||||
if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, args.GetLibraryOptions(), _libraryManager))
|
||||
if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, _libraryManager))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -112,7 +111,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
IDirectoryService directoryService,
|
||||
ILogger logger,
|
||||
IFileSystem fileSystem,
|
||||
LibraryOptions libraryOptions,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
var discSubfolderCount = 0;
|
||||
@ -132,7 +130,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
}
|
||||
|
||||
var path = fileSystemInfo.FullName;
|
||||
var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryOptions, libraryManager);
|
||||
var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager);
|
||||
|
||||
if (hasMusic)
|
||||
{
|
||||
@ -153,7 +151,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
{
|
||||
var fullName = fileSystemInfo.FullName;
|
||||
|
||||
if (libraryManager.IsAudioFile(fullName, libraryOptions))
|
||||
if (libraryManager.IsAudioFile(fullName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -80,14 +80,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
}
|
||||
|
||||
// Avoid mis-identifying top folders
|
||||
if (args.Parent.IsRoot) return null;
|
||||
if (args.Parent.IsRoot)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var directoryService = args.DirectoryService;
|
||||
|
||||
var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager);
|
||||
|
||||
// If we contain an album assume we are an artist folder
|
||||
return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService, args.GetLibraryOptions())) ? new MusicArtist() : null;
|
||||
return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService)) ? new MusicArtist() : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,6 +80,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
if (IsBluRayDirectory(child.FullName, filename, args.DirectoryService))
|
||||
{
|
||||
videoInfo = parser.ResolveDirectory(args.Path);
|
||||
@ -137,7 +138,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
return null;
|
||||
}
|
||||
|
||||
if (LibraryManager.IsVideoFile(args.Path, args.GetLibraryOptions()) || videoInfo.IsStub)
|
||||
if (LibraryManager.IsVideoFile(args.Path) || videoInfo.IsStub)
|
||||
{
|
||||
var path = args.Path;
|
||||
|
||||
|
@ -436,7 +436,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
if (result.Items.Count == 1)
|
||||
{
|
||||
var videoPath = result.Items[0].Path;
|
||||
var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(LibraryManager, libraryOptions, videoPath, i.Name));
|
||||
var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(LibraryManager, videoPath, i.Name));
|
||||
|
||||
if (!hasPhotos)
|
||||
{
|
||||
@ -446,8 +446,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
return movie;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.Items.Count == 0 && multiDiscFolders.Count > 0)
|
||||
else if (result.Items.Count == 0 && multiDiscFolders.Count > 0)
|
||||
{
|
||||
return GetMultiDiscMovie<T>(multiDiscFolders, directoryService);
|
||||
}
|
||||
@ -519,14 +518,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
return null;
|
||||
}
|
||||
|
||||
int additionalPartsLen = folderPaths.Count - 1;
|
||||
var additionalParts = new string[additionalPartsLen];
|
||||
folderPaths.CopyTo(1, additionalParts, 0, additionalPartsLen);
|
||||
|
||||
var returnVideo = new T
|
||||
{
|
||||
Path = folderPaths[0],
|
||||
|
||||
AdditionalParts = folderPaths.Skip(1).ToArray(),
|
||||
|
||||
AdditionalParts = additionalParts,
|
||||
VideoType = videoTypes[0],
|
||||
|
||||
Name = result[0].Name
|
||||
};
|
||||
|
||||
|
@ -63,13 +63,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
if (!file.IsDirectory && PhotoResolver.IsImageFile(file.FullName, _imageProcessor))
|
||||
{
|
||||
var libraryOptions = args.GetLibraryOptions();
|
||||
var filename = file.Name;
|
||||
var ownedByMedia = false;
|
||||
|
||||
foreach (var siblingFile in files)
|
||||
{
|
||||
if (PhotoResolver.IsOwnedByMedia(_libraryManager, libraryOptions, siblingFile.FullName, filename))
|
||||
if (PhotoResolver.IsOwnedByMedia(_libraryManager, siblingFile.FullName, filename))
|
||||
{
|
||||
ownedByMedia = true;
|
||||
break;
|
||||
|
@ -8,7 +8,6 @@ using System.Linq;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers
|
||||
@ -57,11 +56,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
|
||||
// Make sure the image doesn't belong to a video file
|
||||
var files = args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path));
|
||||
var libraryOptions = args.GetLibraryOptions();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (IsOwnedByMedia(_libraryManager, libraryOptions, file.FullName, filename))
|
||||
if (IsOwnedByMedia(_libraryManager, file.FullName, filename))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -78,17 +76,17 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static bool IsOwnedByMedia(ILibraryManager libraryManager, LibraryOptions libraryOptions, string file, string imageFilename)
|
||||
internal static bool IsOwnedByMedia(ILibraryManager libraryManager, string file, string imageFilename)
|
||||
{
|
||||
if (libraryManager.IsVideoFile(file, libraryOptions))
|
||||
if (libraryManager.IsVideoFile(file))
|
||||
{
|
||||
return IsOwnedByResolvedMedia(libraryManager, libraryOptions, file, imageFilename);
|
||||
return IsOwnedByResolvedMedia(libraryManager, file, imageFilename);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, LibraryOptions libraryOptions, string file, string imageFilename)
|
||||
internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, string file, string imageFilename)
|
||||
=> imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
|
||||
|
@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
return null;
|
||||
}
|
||||
|
||||
if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, args.GetLibraryOptions(), false))
|
||||
if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, false))
|
||||
{
|
||||
return new Series
|
||||
{
|
||||
@ -123,24 +123,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
IFileSystem fileSystem,
|
||||
ILogger logger,
|
||||
ILibraryManager libraryManager,
|
||||
LibraryOptions libraryOptions,
|
||||
bool isTvContentType)
|
||||
{
|
||||
foreach (var child in fileSystemChildren)
|
||||
{
|
||||
//if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
|
||||
//{
|
||||
// //logger.LogDebug("Igoring series file or folder marked hidden: {0}", child.FullName);
|
||||
// continue;
|
||||
//}
|
||||
|
||||
// Can't enforce this because files saved by Bitcasa are always marked System
|
||||
//if ((attributes & FileAttributes.System) == FileAttributes.System)
|
||||
//{
|
||||
// logger.LogDebug("Igoring series subfolder marked system: {0}", child.FullName);
|
||||
// continue;
|
||||
//}
|
||||
|
||||
if (child.IsDirectory)
|
||||
{
|
||||
if (IsSeasonFolder(child.FullName, isTvContentType, libraryManager))
|
||||
@ -152,7 +138,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
else
|
||||
{
|
||||
string fullName = child.FullName;
|
||||
if (libraryManager.IsVideoFile(fullName, libraryOptions))
|
||||
if (libraryManager.IsVideoFile(fullName))
|
||||
{
|
||||
if (isTvContentType)
|
||||
{
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
|
@ -30,7 +30,6 @@ using MediaBrowser.Model.Diagnostics;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
|
||||
@ -5,11 +8,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
public class EntryPoint : IServerEntryPoint
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task RunAsync()
|
||||
{
|
||||
return EmbyTV.Current.Start();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
@ -21,7 +24,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
if (info.SeasonNumber.HasValue && info.EpisodeNumber.HasValue)
|
||||
{
|
||||
name += string.Format(" S{0}E{1}", info.SeasonNumber.Value.ToString("00", CultureInfo.InvariantCulture), info.EpisodeNumber.Value.ToString("00", CultureInfo.InvariantCulture));
|
||||
name += string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
" S{0}E{1}",
|
||||
info.SeasonNumber.Value.ToString("00", CultureInfo.InvariantCulture),
|
||||
info.EpisodeNumber.Value.ToString("00", CultureInfo.InvariantCulture));
|
||||
addHyphen = false;
|
||||
}
|
||||
else if (info.OriginalAirDate.HasValue)
|
||||
@ -32,7 +39,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
}
|
||||
else
|
||||
{
|
||||
name += " " + info.OriginalAirDate.Value.ToLocalTime().ToString("yyyy-MM-dd");
|
||||
name += " " + info.OriginalAirDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -67,14 +74,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
date = date.ToLocalTime();
|
||||
|
||||
return string.Format("{0}_{1}_{2}_{3}_{4}_{5}",
|
||||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}_{1}_{2}_{3}_{4}_{5}",
|
||||
date.Year.ToString("0000", CultureInfo.InvariantCulture),
|
||||
date.Month.ToString("00", CultureInfo.InvariantCulture),
|
||||
date.Day.ToString("00", CultureInfo.InvariantCulture),
|
||||
date.Hour.ToString("00", CultureInfo.InvariantCulture),
|
||||
date.Minute.ToString("00", CultureInfo.InvariantCulture),
|
||||
date.Second.ToString("00", CultureInfo.InvariantCulture)
|
||||
);
|
||||
date.Second.ToString("00", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
@ -12,6 +15,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Add(SeriesTimerInfo item)
|
||||
{
|
||||
if (string.IsNullOrEmpty(item.Id))
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@ -91,12 +94,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
using (var gzStream = new GZipStream(stream, CompressionMode.Decompress))
|
||||
{
|
||||
await gzStream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
await gzStream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
@ -1,5 +1,5 @@
|
||||
#pragma warning disable SA1600
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,52 +1,48 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
public class LiveTvMediaSourceProvider : IMediaSourceProvider
|
||||
{
|
||||
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
|
||||
private const char StreamIdDelimeter = '_';
|
||||
private const string StreamIdDelimeterString = "_";
|
||||
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private IApplicationPaths _appPaths;
|
||||
|
||||
public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILoggerFactory loggerFactory, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, IServerApplicationHost appHost)
|
||||
public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, ILogger<LiveTvMediaSourceProvider> logger, IMediaSourceManager mediaSourceManager, IServerApplicationHost appHost)
|
||||
{
|
||||
_liveTvManager = liveTvManager;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_logger = logger;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_appHost = appHost;
|
||||
_logger = loggerFactory.CreateLogger(GetType().Name);
|
||||
_appPaths = appPaths;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
var baseItem = (BaseItem)item;
|
||||
|
||||
if (baseItem.SourceType == SourceType.LiveTV)
|
||||
if (item.SourceType == SourceType.LiveTV)
|
||||
{
|
||||
var activeRecordingInfo = _liveTvManager.GetActiveRecordingInfo(item.Path);
|
||||
|
||||
if (string.IsNullOrEmpty(baseItem.Path) || activeRecordingInfo != null)
|
||||
if (string.IsNullOrEmpty(item.Path) || activeRecordingInfo != null)
|
||||
{
|
||||
return GetMediaSourcesInternal(item, activeRecordingInfo, cancellationToken);
|
||||
}
|
||||
@ -55,10 +51,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
return Task.FromResult<IEnumerable<MediaSourceInfo>>(Array.Empty<MediaSourceInfo>());
|
||||
}
|
||||
|
||||
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
|
||||
private const char StreamIdDelimeter = '_';
|
||||
private const string StreamIdDelimeterString = "_";
|
||||
|
||||
private async Task<IEnumerable<MediaSourceInfo>> GetMediaSourcesInternal(BaseItem item, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
IEnumerable<MediaSourceInfo> sources;
|
||||
@ -91,7 +83,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
foreach (var source in list)
|
||||
{
|
||||
source.Type = MediaSourceType.Default;
|
||||
source.BufferMs = source.BufferMs ?? 1500;
|
||||
source.BufferMs ??= 1500;
|
||||
|
||||
if (source.RequiresOpening || forceRequireOpening)
|
||||
{
|
||||
@ -100,11 +92,14 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
if (source.RequiresOpening)
|
||||
{
|
||||
var openKeys = new List<string>();
|
||||
openKeys.Add(item.GetType().Name);
|
||||
openKeys.Add(item.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
openKeys.Add(source.Id ?? string.Empty);
|
||||
source.OpenToken = string.Join(StreamIdDelimeterString, openKeys.ToArray());
|
||||
var openKeys = new List<string>
|
||||
{
|
||||
item.GetType().Name,
|
||||
item.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||
source.Id ?? string.Empty
|
||||
};
|
||||
|
||||
source.OpenToken = string.Join(StreamIdDelimeterString, openKeys);
|
||||
}
|
||||
|
||||
// Dummy this up so that direct play checks can still run
|
||||
@ -114,11 +109,12 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogDebug("MediaSources: {0}", _jsonSerializer.SerializeToString(list));
|
||||
_logger.LogDebug("MediaSources: {@MediaSources}", list);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ILiveStream> OpenMediaSource(string openToken, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
|
||||
{
|
||||
var keys = openToken.Split(new[] { StreamIdDelimeter }, 3);
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -3,7 +3,7 @@
|
||||
"ItemRemovedWithName": "{0} var fjarlægt úr safninu",
|
||||
"ItemAddedWithName": "{0} var bætt í safnið",
|
||||
"Inherit": "Erfa",
|
||||
"HomeVideos": "Myndbönd að heiman",
|
||||
"HomeVideos": "Heimamyndbönd",
|
||||
"HeaderRecordingGroups": "Upptökuhópar",
|
||||
"HeaderNextUp": "Næst á dagskrá",
|
||||
"HeaderLiveTV": "Sjónvarp í beinni útsendingu",
|
||||
@ -36,10 +36,10 @@
|
||||
"NotificationOptionVideoPlaybackStopped": "Myndbandafspilun stöðvuð",
|
||||
"NotificationOptionVideoPlayback": "Myndbandafspilun hafin",
|
||||
"NotificationOptionUserLockedOut": "Notandi læstur úti",
|
||||
"NotificationOptionServerRestartRequired": "Endurræsing miðlara nauðsynileg",
|
||||
"NotificationOptionServerRestartRequired": "Endurræsing þjóns er nauðsynileg",
|
||||
"NotificationOptionPluginUpdateInstalled": "Viðbótar uppfærsla uppsett",
|
||||
"NotificationOptionPluginUninstalled": "Viðbót fjarlægð",
|
||||
"NotificationOptionPluginInstalled": "Viðbót settur upp",
|
||||
"NotificationOptionPluginInstalled": "Viðbót sett upp",
|
||||
"NotificationOptionPluginError": "Bilun í viðbót",
|
||||
"NotificationOptionInstallationFailed": "Uppsetning tókst ekki",
|
||||
"NotificationOptionCameraImageUploaded": "Myndavélarmynd hlaðið upp",
|
||||
@ -50,15 +50,15 @@
|
||||
"NameSeasonUnknown": "Sería óþekkt",
|
||||
"NameSeasonNumber": "Sería {0}",
|
||||
"MixedContent": "Blandað efni",
|
||||
"MessageServerConfigurationUpdated": "Stillingar miðlarans hefur verið uppfærð",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server hefur verið uppfærður í {0}",
|
||||
"MessageApplicationUpdated": "Jellyfin Server hefur verið uppfærður",
|
||||
"MessageServerConfigurationUpdated": "Stillingar þjóns hafa verið uppfærðar",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin þjónn hefur verið uppfærður í {0}",
|
||||
"MessageApplicationUpdated": "Jellyfin þjónn hefur verið uppfærður",
|
||||
"Latest": "Nýjasta",
|
||||
"LabelRunningTimeValue": "Keyrslutími kerfis: {0}",
|
||||
"LabelRunningTimeValue": "spilunartími: {0}",
|
||||
"User": "Notandi",
|
||||
"System": "Kerfi",
|
||||
"NotificationOptionNewLibraryContent": "Nýju efni bætt við",
|
||||
"NewVersionIsAvailable": "Ný útgáfa af Jellyfin Server er fáanleg til niðurhals.",
|
||||
"NewVersionIsAvailable": "Ný útgáfa af Jellyfin þjón er fáanleg til niðurhals.",
|
||||
"NameInstallFailed": "{0} uppsetning mistókst",
|
||||
"MusicVideos": "Tónlistarmyndbönd",
|
||||
"Music": "Tónlist",
|
||||
@ -74,5 +74,23 @@
|
||||
"PluginUpdatedWithName": "{0} var uppfært",
|
||||
"PluginUninstalledWithName": "{0} var fjarlægt",
|
||||
"PluginInstalledWithName": "{0} var sett upp",
|
||||
"NotificationOptionTaskFailed": "Tímasett verkefni mistókst"
|
||||
"NotificationOptionTaskFailed": "Tímasett verkefni mistókst",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin netþjónnin er að hlaðast. Vinsamlega prufaðu aftur fljótlega.",
|
||||
"VersionNumber": "Útgáfa {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} hefur verið bætt við í gagnasafnið þitt",
|
||||
"UserStoppedPlayingItemWithValues": "{0} hefur lokið spilunar af {1} á {2}",
|
||||
"UserStartedPlayingItemWithValues": "{0} er að spila {1} á {2}",
|
||||
"UserPolicyUpdatedWithName": "Notandaregla hefur verið uppfærð fyrir notanda {0}",
|
||||
"UserPasswordChangedWithName": "Lykilorði fyrir notandann {0} hefur verið breytt",
|
||||
"UserOnlineFromDevice": "{0} hefur verið virkur síðan {1}",
|
||||
"UserOfflineFromDevice": "{0} hefur aftengst frá {1}",
|
||||
"UserLockedOutWithName": "Notanda {0} hefur verið hindraður aðgangur",
|
||||
"UserDownloadingItemWithValues": "{0} Hleður niður {1}",
|
||||
"SubtitlesDownloadedForItem": "Skjátextum halað niður fyrir {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Tókst ekki að hala niður skjátextum frá {0} til {1}",
|
||||
"ProviderValue": "Veitandi: {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Stilling {0} hefur verið uppfærð á netþjón",
|
||||
"ValueSpecialEpisodeName": "Sérstakt - {0}",
|
||||
"Shows": "Þættir",
|
||||
"Playlists": "Spilunarlisti"
|
||||
}
|
||||
|
1
Emby.Server.Implementations/Localization/Core/nn.json
Normal file
1
Emby.Server.Implementations/Localization/Core/nn.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
@ -62,8 +62,43 @@ namespace Emby.Server.Implementations.Session
|
||||
private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections =
|
||||
new ConcurrentDictionary<string, SessionInfo>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private Timer _idleTimer;
|
||||
|
||||
private DtoOptions _itemInfoDtoOptions;
|
||||
private bool _disposed = false;
|
||||
|
||||
public SessionManager(
|
||||
ILogger<SessionManager> logger,
|
||||
IUserDataManager userDataManager,
|
||||
ILibraryManager libraryManager,
|
||||
IUserManager userManager,
|
||||
IMusicManager musicManager,
|
||||
IDtoService dtoService,
|
||||
IImageProcessor imageProcessor,
|
||||
IServerApplicationHost appHost,
|
||||
IAuthenticationRepository authRepo,
|
||||
IDeviceManager deviceManager,
|
||||
IMediaSourceManager mediaSourceManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_userDataManager = userDataManager;
|
||||
_libraryManager = libraryManager;
|
||||
_userManager = userManager;
|
||||
_musicManager = musicManager;
|
||||
_dtoService = dtoService;
|
||||
_imageProcessor = imageProcessor;
|
||||
_appHost = appHost;
|
||||
_authRepo = authRepo;
|
||||
_deviceManager = deviceManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
|
||||
_deviceManager.DeviceOptionsUpdated += OnDeviceManagerDeviceOptionsUpdated;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<GenericEventArgs<AuthenticationRequest>> AuthenticationFailed;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<GenericEventArgs<AuthenticationResult>> AuthenticationSucceeded;
|
||||
|
||||
/// <summary>
|
||||
@ -81,40 +116,23 @@ namespace Emby.Server.Implementations.Session
|
||||
/// </summary>
|
||||
public event EventHandler<PlaybackStopEventArgs> PlaybackStopped;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<SessionEventArgs> SessionStarted;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<SessionEventArgs> CapabilitiesChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<SessionEventArgs> SessionEnded;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<SessionEventArgs> SessionActivity;
|
||||
|
||||
public SessionManager(
|
||||
IUserDataManager userDataManager,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILibraryManager libraryManager,
|
||||
IUserManager userManager,
|
||||
IMusicManager musicManager,
|
||||
IDtoService dtoService,
|
||||
IImageProcessor imageProcessor,
|
||||
IServerApplicationHost appHost,
|
||||
IAuthenticationRepository authRepo,
|
||||
IDeviceManager deviceManager,
|
||||
IMediaSourceManager mediaSourceManager)
|
||||
{
|
||||
_userDataManager = userDataManager;
|
||||
_logger = loggerFactory.CreateLogger(nameof(SessionManager));
|
||||
_libraryManager = libraryManager;
|
||||
_userManager = userManager;
|
||||
_musicManager = musicManager;
|
||||
_dtoService = dtoService;
|
||||
_imageProcessor = imageProcessor;
|
||||
_appHost = appHost;
|
||||
_authRepo = authRepo;
|
||||
_deviceManager = deviceManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_deviceManager.DeviceOptionsUpdated += OnDeviceManagerDeviceOptionsUpdated;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets all connections.
|
||||
/// </summary>
|
||||
/// <value>All connections.</value>
|
||||
public IEnumerable<SessionInfo> Sessions => _activeConnections.Values.OrderByDescending(c => c.LastActivityDate);
|
||||
|
||||
private void OnDeviceManagerDeviceOptionsUpdated(object sender, GenericEventArgs<Tuple<string, DeviceOptions>> e)
|
||||
{
|
||||
@ -135,14 +153,17 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
}
|
||||
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and optionally managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
@ -152,15 +173,17 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
// TODO: dispose stuff
|
||||
_idleTimer?.Dispose();
|
||||
}
|
||||
|
||||
_idleTimer = null;
|
||||
|
||||
_deviceManager.DeviceOptionsUpdated -= OnDeviceManagerDeviceOptionsUpdated;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void CheckDisposed()
|
||||
private void CheckDisposed()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
@ -168,12 +191,6 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all connections.
|
||||
/// </summary>
|
||||
/// <value>All connections.</value>
|
||||
public IEnumerable<SessionInfo> Sessions => _activeConnections.Values.OrderByDescending(c => c.LastActivityDate);
|
||||
|
||||
private void OnSessionStarted(SessionInfo info)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(info.DeviceId))
|
||||
@ -204,13 +221,13 @@ namespace Emby.Server.Implementations.Session
|
||||
new SessionEventArgs
|
||||
{
|
||||
SessionInfo = info
|
||||
|
||||
},
|
||||
_logger);
|
||||
|
||||
info.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateDeviceName(string sessionId, string deviceName)
|
||||
{
|
||||
var session = GetSession(sessionId);
|
||||
@ -230,7 +247,6 @@ namespace Emby.Server.Implementations.Session
|
||||
/// <param name="remoteEndPoint">The remote end point.</param>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>SessionInfo.</returns>
|
||||
/// <exception cref="ArgumentNullException">user</exception>
|
||||
public SessionInfo LogSessionActivity(
|
||||
string appName,
|
||||
string appVersion,
|
||||
@ -268,14 +284,7 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
if ((activityDate - userLastActivityDate).TotalSeconds > 60)
|
||||
{
|
||||
try
|
||||
{
|
||||
_userManager.UpdateUser(user);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Error updating user", ex);
|
||||
}
|
||||
_userManager.UpdateUser(user);
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,18 +301,20 @@ namespace Emby.Server.Implementations.Session
|
||||
return session;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CloseIfNeeded(SessionInfo session)
|
||||
{
|
||||
if (!session.SessionControllers.Any(i => i.IsSessionActive))
|
||||
{
|
||||
var key = GetSessionKey(session.Client, session.DeviceId);
|
||||
|
||||
_activeConnections.TryRemove(key, out var removed);
|
||||
_activeConnections.TryRemove(key, out _);
|
||||
|
||||
OnSessionEnded(session);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReportSessionEnded(string sessionId)
|
||||
{
|
||||
CheckDisposed();
|
||||
@ -313,7 +324,7 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
var key = GetSessionKey(session.Client, session.DeviceId);
|
||||
|
||||
_activeConnections.TryRemove(key, out var removed);
|
||||
_activeConnections.TryRemove(key, out _);
|
||||
|
||||
OnSessionEnded(session);
|
||||
}
|
||||
@ -344,7 +355,7 @@ namespace Emby.Server.Implementations.Session
|
||||
var runtimeTicks = libraryItem.RunTimeTicks;
|
||||
|
||||
MediaSourceInfo mediaSource = null;
|
||||
if (libraryItem is IHasMediaSources hasMediaSources)
|
||||
if (libraryItem is IHasMediaSources)
|
||||
{
|
||||
mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false);
|
||||
|
||||
@ -396,7 +407,6 @@ namespace Emby.Server.Implementations.Session
|
||||
/// Removes the now playing item id.
|
||||
/// </summary>
|
||||
/// <param name="session">The session.</param>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
private void RemoveNowPlayingItem(SessionInfo session)
|
||||
{
|
||||
session.NowPlayingItem = null;
|
||||
@ -409,9 +419,7 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
|
||||
private static string GetSessionKey(string appName, string deviceId)
|
||||
{
|
||||
return appName + deviceId;
|
||||
}
|
||||
=> appName + deviceId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the connection.
|
||||
@ -431,6 +439,7 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
throw new ArgumentNullException(nameof(deviceId));
|
||||
}
|
||||
|
||||
var key = GetSessionKey(appName, deviceId);
|
||||
|
||||
CheckDisposed();
|
||||
@ -503,7 +512,7 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
var users = new List<User>();
|
||||
|
||||
if (!session.UserId.Equals(Guid.Empty))
|
||||
if (session.UserId != Guid.Empty)
|
||||
{
|
||||
var user = _userManager.GetUserById(session.UserId);
|
||||
|
||||
@ -522,8 +531,6 @@ namespace Emby.Server.Implementations.Session
|
||||
return users;
|
||||
}
|
||||
|
||||
private Timer _idleTimer;
|
||||
|
||||
private void StartIdleCheckTimer()
|
||||
{
|
||||
if (_idleTimer == null)
|
||||
@ -599,11 +606,11 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to report that playback has started for an item
|
||||
/// Used to report that playback has started for an item.
|
||||
/// </summary>
|
||||
/// <param name="info">The info.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="ArgumentNullException">info</exception>
|
||||
/// <exception cref="ArgumentNullException"><c>info</c> is <c>null</c>.</exception>
|
||||
public async Task OnPlaybackStart(PlaybackStartInfo info)
|
||||
{
|
||||
CheckDisposed();
|
||||
@ -615,7 +622,7 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
var session = GetSession(info.SessionId);
|
||||
|
||||
var libraryItem = info.ItemId.Equals(Guid.Empty)
|
||||
var libraryItem = info.ItemId == Guid.Empty
|
||||
? null
|
||||
: GetNowPlayingItem(session, info.ItemId);
|
||||
|
||||
@ -653,7 +660,6 @@ namespace Emby.Server.Implementations.Session
|
||||
ClientName = session.Client,
|
||||
DeviceId = session.DeviceId,
|
||||
Session = session
|
||||
|
||||
},
|
||||
_logger);
|
||||
|
||||
@ -684,6 +690,7 @@ namespace Emby.Server.Implementations.Session
|
||||
_userDataManager.SaveUserData(user, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task OnPlaybackProgress(PlaybackProgressInfo info)
|
||||
{
|
||||
return OnPlaybackProgress(info, false);
|
||||
@ -857,7 +864,7 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
MediaSourceInfo mediaSource = null;
|
||||
|
||||
if (libraryItem is IHasMediaSources hasMediaSources)
|
||||
if (libraryItem is IHasMediaSources)
|
||||
{
|
||||
mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false);
|
||||
}
|
||||
@ -966,13 +973,17 @@ namespace Emby.Server.Implementations.Session
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <param name="throwOnMissing">if set to <c>true</c> [throw on missing].</param>
|
||||
/// <returns>SessionInfo.</returns>
|
||||
/// <exception cref="ResourceNotFoundException">sessionId</exception>
|
||||
/// <exception cref="ResourceNotFoundException">
|
||||
/// No session with an Id equal to <c>sessionId</c> was found
|
||||
/// and <c>throwOnMissing</c> is <c>true</c>.
|
||||
/// </exception>
|
||||
private SessionInfo GetSession(string sessionId, bool throwOnMissing = true)
|
||||
{
|
||||
var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId, StringComparison.Ordinal));
|
||||
if (session == null && throwOnMissing)
|
||||
{
|
||||
throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId));
|
||||
throw new ResourceNotFoundException(
|
||||
string.Format(CultureInfo.InvariantCulture, "Session {0} not found.", sessionId));
|
||||
}
|
||||
|
||||
return session;
|
||||
@ -985,12 +996,14 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
if (session == null)
|
||||
{
|
||||
throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId));
|
||||
throw new ResourceNotFoundException(
|
||||
string.Format(CultureInfo.InvariantCulture, "Session {0} not found.", sessionId));
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendMessageCommand(string controllingSessionId, string sessionId, MessageCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
@ -1011,6 +1024,7 @@ namespace Emby.Server.Implementations.Session
|
||||
return SendGeneralCommand(controllingSessionId, sessionId, generalCommand, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendGeneralCommand(string controllingSessionId, string sessionId, GeneralCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
@ -1055,6 +1069,7 @@ namespace Emby.Server.Implementations.Session
|
||||
return Task.WhenAll(GetTasks());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
@ -1096,7 +1111,8 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
if (items.Any(i => i.GetPlayAccess(user) != PlayAccess.Full))
|
||||
{
|
||||
throw new ArgumentException(string.Format("{0} is not allowed to play media.", user.Name));
|
||||
throw new ArgumentException(
|
||||
string.Format(CultureInfo.InvariantCulture, "{0} is not allowed to play media.", user.Name));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1204,6 +1220,7 @@ namespace Emby.Server.Implementations.Session
|
||||
return _musicManager.GetInstantMixFromItem(item, user, new DtoOptions(false) { EnableImages = false });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendBrowseCommand(string controllingSessionId, string sessionId, BrowseRequest command, CancellationToken cancellationToken)
|
||||
{
|
||||
var generalCommand = new GeneralCommand
|
||||
@ -1220,6 +1237,7 @@ namespace Emby.Server.Implementations.Session
|
||||
return SendGeneralCommand(controllingSessionId, sessionId, generalCommand, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendPlaystateCommand(string controllingSessionId, string sessionId, PlaystateRequest command, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
@ -1303,12 +1321,12 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
var session = GetSession(sessionId);
|
||||
|
||||
if (session.UserId.Equals(userId))
|
||||
if (session.UserId == userId)
|
||||
{
|
||||
throw new ArgumentException("The requested user is already the primary user of the session.");
|
||||
}
|
||||
|
||||
if (session.AdditionalUsers.All(i => !i.UserId.Equals(userId)))
|
||||
if (session.AdditionalUsers.All(i => i.UserId != userId))
|
||||
{
|
||||
var user = _userManager.GetUserById(userId);
|
||||
|
||||
@ -1430,12 +1448,13 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
private string GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName)
|
||||
{
|
||||
var existing = _authRepo.Get(new AuthenticationInfoQuery
|
||||
{
|
||||
DeviceId = deviceId,
|
||||
UserId = user.Id,
|
||||
Limit = 1
|
||||
}).Items.FirstOrDefault();
|
||||
var existing = _authRepo.Get(
|
||||
new AuthenticationInfoQuery
|
||||
{
|
||||
DeviceId = deviceId,
|
||||
UserId = user.Id,
|
||||
Limit = 1
|
||||
}).Items.FirstOrDefault();
|
||||
|
||||
var allExistingForDevice = _authRepo.Get(
|
||||
new AuthenticationInfoQuery
|
||||
@ -1460,7 +1479,7 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
_logger.LogInformation("Reissuing access token: " + existing.AccessToken);
|
||||
_logger.LogInformation("Reissuing access token: {Token}", existing.AccessToken);
|
||||
return existing.AccessToken;
|
||||
}
|
||||
|
||||
@ -1485,6 +1504,7 @@ namespace Emby.Server.Implementations.Session
|
||||
return newToken.AccessToken;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Logout(string accessToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
@ -1494,18 +1514,20 @@ namespace Emby.Server.Implementations.Session
|
||||
throw new ArgumentNullException(nameof(accessToken));
|
||||
}
|
||||
|
||||
var existing = _authRepo.Get(new AuthenticationInfoQuery
|
||||
{
|
||||
Limit = 1,
|
||||
AccessToken = accessToken
|
||||
}).Items.FirstOrDefault();
|
||||
var existing = _authRepo.Get(
|
||||
new AuthenticationInfoQuery
|
||||
{
|
||||
Limit = 1,
|
||||
AccessToken = accessToken
|
||||
}).Items;
|
||||
|
||||
if (existing != null)
|
||||
if (existing.Count > 0)
|
||||
{
|
||||
Logout(existing);
|
||||
Logout(existing[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Logout(AuthenticationInfo existing)
|
||||
{
|
||||
CheckDisposed();
|
||||
@ -1531,6 +1553,7 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RevokeUserTokens(Guid userId, string currentAccessToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
@ -1549,6 +1572,7 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RevokeToken(string token)
|
||||
{
|
||||
Logout(token);
|
||||
@ -1605,8 +1629,6 @@ namespace Emby.Server.Implementations.Session
|
||||
_deviceManager.SaveCapabilities(deviceId, capabilities);
|
||||
}
|
||||
|
||||
private DtoOptions _itemInfoDtoOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a BaseItem to a BaseItemInfo.
|
||||
/// </summary>
|
||||
@ -1683,6 +1705,7 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReportNowViewingItem(string sessionId, string itemId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(itemId))
|
||||
@ -1697,6 +1720,7 @@ namespace Emby.Server.Implementations.Session
|
||||
ReportNowViewingItem(sessionId, info);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReportNowViewingItem(string sessionId, BaseItemDto item)
|
||||
{
|
||||
var session = GetSession(sessionId);
|
||||
@ -1704,6 +1728,7 @@ namespace Emby.Server.Implementations.Session
|
||||
session.NowViewingItem = item;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReportTranscodingInfo(string deviceId, TranscodingInfo info)
|
||||
{
|
||||
var session = Sessions.FirstOrDefault(i =>
|
||||
@ -1715,11 +1740,13 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ClearTranscodingInfo(string deviceId)
|
||||
{
|
||||
ReportTranscodingInfo(deviceId, null);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public SessionInfo GetSession(string deviceId, string client, string version)
|
||||
{
|
||||
return Sessions.FirstOrDefault(i =>
|
||||
@ -1727,6 +1754,7 @@ namespace Emby.Server.Implementations.Session
|
||||
&& string.Equals(i.Client, client, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public SessionInfo GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion)
|
||||
{
|
||||
if (info == null)
|
||||
@ -1759,23 +1787,24 @@ namespace Emby.Server.Implementations.Session
|
||||
return LogSessionActivity(appName, appVersion, deviceId, deviceName, remoteEndpoint, user);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public SessionInfo GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint)
|
||||
{
|
||||
var result = _authRepo.Get(new AuthenticationInfoQuery
|
||||
var items = _authRepo.Get(new AuthenticationInfoQuery
|
||||
{
|
||||
AccessToken = token
|
||||
});
|
||||
AccessToken = token,
|
||||
Limit = 1
|
||||
}).Items;
|
||||
|
||||
var info = result.Items.FirstOrDefault();
|
||||
|
||||
if (info == null)
|
||||
if (items.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetSessionByAuthenticationToken(info, deviceId, remoteEndpoint, null);
|
||||
return GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendMessageToAdminSessions<T>(string name, T data, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
@ -1785,6 +1814,7 @@ namespace Emby.Server.Implementations.Session
|
||||
return SendMessageToUserSessions(adminUserIds, name, data, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, Func<T> dataFn, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
@ -1796,11 +1826,10 @@ namespace Emby.Server.Implementations.Session
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var data = dataFn();
|
||||
|
||||
return SendMessageToSessions(sessions, name, data, cancellationToken);
|
||||
return SendMessageToSessions(sessions, name, dataFn(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, T data, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
@ -1809,6 +1838,7 @@ namespace Emby.Server.Implementations.Session
|
||||
return SendMessageToSessions(sessions, name, data, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendMessageToUserDeviceSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
@ -1817,22 +1847,5 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
return SendMessageToSessions(sessions, name, data, cancellationToken);
|
||||
}
|
||||
|
||||
public Task SendMessageToUserDeviceAndAdminSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
var sessions = Sessions
|
||||
.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i));
|
||||
|
||||
return SendMessageToSessions(sessions, name, data, cancellationToken);
|
||||
}
|
||||
|
||||
private bool IsAdminSession(SessionInfo s)
|
||||
{
|
||||
var user = _userManager.GetUserById(s.UserId);
|
||||
|
||||
return user != null && user.Policy.IsAdministrator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SkiaSharp;
|
||||
using static Jellyfin.Drawing.Skia.SkiaHelper;
|
||||
@ -18,27 +17,23 @@ namespace Jellyfin.Drawing.Skia
|
||||
/// </summary>
|
||||
public class SkiaEncoder : IImageEncoder
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly ILocalizationManager _localizationManager;
|
||||
|
||||
private static readonly HashSet<string> _transparentImageTypes
|
||||
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkiaEncoder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The application logger.</param>
|
||||
/// <param name="appPaths">The application paths.</param>
|
||||
/// <param name="localizationManager">The application localization manager.</param>
|
||||
public SkiaEncoder(
|
||||
ILogger<SkiaEncoder> logger,
|
||||
IApplicationPaths appPaths,
|
||||
ILocalizationManager localizationManager)
|
||||
IApplicationPaths appPaths)
|
||||
{
|
||||
_logger = logger;
|
||||
_appPaths = appPaths;
|
||||
_localizationManager = localizationManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@ -235,9 +230,12 @@ namespace Jellyfin.Drawing.Skia
|
||||
|
||||
private bool RequiresSpecialCharacterHack(string path)
|
||||
{
|
||||
if (_localizationManager.HasUnicodeCategory(path, UnicodeCategory.OtherLetter))
|
||||
for (int i = 0; i < path.Length; i++)
|
||||
{
|
||||
return true;
|
||||
if (char.GetUnicodeCategory(path[i]) == UnicodeCategory.OtherLetter)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (HasDiacritics(path))
|
||||
|
@ -168,7 +168,7 @@ namespace Jellyfin.Server
|
||||
_loggerFactory,
|
||||
options,
|
||||
new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
|
||||
new NullImageEncoder(),
|
||||
GetImageEncoder(appPaths),
|
||||
new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()),
|
||||
appConfig);
|
||||
try
|
||||
@ -192,8 +192,6 @@ namespace Jellyfin.Server
|
||||
throw;
|
||||
}
|
||||
|
||||
appHost.ImageProcessor.ImageEncoder = GetImageEncoder(appPaths, appHost.LocalizationManager);
|
||||
|
||||
await appHost.RunStartupTasksAsync().ConfigureAwait(false);
|
||||
|
||||
stopWatch.Stop();
|
||||
@ -491,9 +489,7 @@ namespace Jellyfin.Server
|
||||
}
|
||||
}
|
||||
|
||||
private static IImageEncoder GetImageEncoder(
|
||||
IApplicationPaths appPaths,
|
||||
ILocalizationManager localizationManager)
|
||||
private static IImageEncoder GetImageEncoder(IApplicationPaths appPaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -502,8 +498,7 @@ namespace Jellyfin.Server
|
||||
|
||||
return new SkiaEncoder(
|
||||
_loggerFactory.CreateLogger<SkiaEncoder>(),
|
||||
appPaths,
|
||||
localizationManager);
|
||||
appPaths);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -24,7 +24,7 @@ using Microsoft.Net.Http.Headers;
|
||||
namespace MediaBrowser.Api.Images
|
||||
{
|
||||
/// <summary>
|
||||
/// Class GetItemImage
|
||||
/// Class GetItemImage.
|
||||
/// </summary>
|
||||
[Route("/Items/{Id}/Images", "GET", Summary = "Gets information about an item's images")]
|
||||
[Authenticated]
|
||||
@ -558,21 +558,6 @@ namespace MediaBrowser.Api.Images
|
||||
throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", displayText, request.Type));
|
||||
}
|
||||
|
||||
IImageEnhancer[] supportedImageEnhancers;
|
||||
if (_imageProcessor.ImageEnhancers.Count > 0)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
item = _libraryManager.GetItemById(itemId);
|
||||
}
|
||||
|
||||
supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.GetSupportedEnhancers(item, request.Type).ToArray() : Array.Empty<IImageEnhancer>();
|
||||
}
|
||||
else
|
||||
{
|
||||
supportedImageEnhancers = Array.Empty<IImageEnhancer>();
|
||||
}
|
||||
|
||||
bool cropwhitespace;
|
||||
if (request.CropWhitespace.HasValue)
|
||||
{
|
||||
@ -598,25 +583,25 @@ namespace MediaBrowser.Api.Images
|
||||
{"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"}
|
||||
};
|
||||
|
||||
return GetImageResult(item,
|
||||
return GetImageResult(
|
||||
item,
|
||||
itemId,
|
||||
request,
|
||||
imageInfo,
|
||||
cropwhitespace,
|
||||
outputFormats,
|
||||
supportedImageEnhancers,
|
||||
cacheDuration,
|
||||
responseHeaders,
|
||||
isHeadRequest);
|
||||
}
|
||||
|
||||
private async Task<object> GetImageResult(BaseItem item,
|
||||
private async Task<object> GetImageResult(
|
||||
BaseItem item,
|
||||
Guid itemId,
|
||||
ImageRequest request,
|
||||
ItemImageInfo image,
|
||||
bool cropwhitespace,
|
||||
IReadOnlyCollection<ImageFormat> supportedFormats,
|
||||
IReadOnlyCollection<IImageEnhancer> enhancers,
|
||||
TimeSpan? cacheDuration,
|
||||
IDictionary<string, string> headers,
|
||||
bool isHeadRequest)
|
||||
@ -624,7 +609,6 @@ namespace MediaBrowser.Api.Images
|
||||
var options = new ImageProcessingOptions
|
||||
{
|
||||
CropWhiteSpace = cropwhitespace,
|
||||
Enhancers = enhancers,
|
||||
Height = request.Height,
|
||||
ImageIndex = request.Index ?? 0,
|
||||
Image = image,
|
||||
|
@ -250,11 +250,11 @@ namespace MediaBrowser.Api.Playback
|
||||
{
|
||||
if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
logFilePrefix = "ffmpeg-directstream";
|
||||
logFilePrefix = "ffmpeg-remux";
|
||||
}
|
||||
else
|
||||
{
|
||||
logFilePrefix = "ffmpeg-remux";
|
||||
logFilePrefix = "ffmpeg-directstream";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
@ -20,20 +19,12 @@ namespace MediaBrowser.Controller.Drawing
|
||||
/// <value>The supported input formats.</value>
|
||||
IReadOnlyCollection<string> SupportedInputFormats { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the image enhancers.
|
||||
/// </summary>
|
||||
/// <value>The image enhancers.</value>
|
||||
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.
|
||||
/// </summary>
|
||||
@ -58,14 +49,6 @@ namespace MediaBrowser.Controller.Drawing
|
||||
/// <returns>ImageDimensions</returns>
|
||||
ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the supported enhancers.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="imageType">Type of the image.</param>
|
||||
/// <returns>IEnumerable{IImageEnhancer}.</returns>
|
||||
IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the image cache tag.
|
||||
/// </summary>
|
||||
@ -75,15 +58,6 @@ namespace MediaBrowser.Controller.Drawing
|
||||
string GetImageCacheTag(BaseItem item, ItemImageInfo image);
|
||||
string GetImageCacheTag(BaseItem item, ChapterInfo info);
|
||||
|
||||
/// <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>
|
||||
string GetImageCacheTag(BaseItem item, ItemImageInfo image, IReadOnlyCollection<IImageEnhancer> imageEnhancers);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the image.
|
||||
/// </summary>
|
||||
@ -99,15 +73,6 @@ namespace MediaBrowser.Controller.Drawing
|
||||
/// <returns>Task.</returns>
|
||||
Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options);
|
||||
|
||||
/// <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>
|
||||
Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the supported image output formats.
|
||||
/// </summary>
|
||||
|
@ -19,8 +19,6 @@ namespace MediaBrowser.Controller.Drawing
|
||||
return GetSizeEstimate(options);
|
||||
}
|
||||
|
||||
public static IImageProcessor ImageProcessor { get; set; }
|
||||
|
||||
private static ImageDimensions GetSizeEstimate(ImageProcessingOptions options)
|
||||
{
|
||||
if (options.Width.HasValue && options.Height.HasValue)
|
||||
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
|
||||
namespace MediaBrowser.Controller.Drawing
|
||||
@ -34,8 +33,6 @@ namespace MediaBrowser.Controller.Drawing
|
||||
|
||||
public int Quality { get; set; }
|
||||
|
||||
public IReadOnlyCollection<IImageEnhancer> Enhancers { get; set; }
|
||||
|
||||
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; set; }
|
||||
|
||||
public bool AddPlayedIndicator { get; set; }
|
||||
|
@ -41,10 +41,10 @@ namespace MediaBrowser.Controller.Entities
|
||||
public override double GetDefaultPrimaryImageAspectRatio()
|
||||
{
|
||||
// REVIEW: @bond
|
||||
if (Width.HasValue && Height.HasValue)
|
||||
if (Width != 0 && Height != 0)
|
||||
{
|
||||
double width = Width.Value;
|
||||
double height = Height.Value;
|
||||
double width = Width;
|
||||
double height = Height;
|
||||
|
||||
if (Orientation.HasValue)
|
||||
{
|
||||
@ -67,8 +67,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
return base.GetDefaultPrimaryImageAspectRatio();
|
||||
}
|
||||
|
||||
public new int? Width { get; set; }
|
||||
public new int? Height { get; set; }
|
||||
public string CameraMake { get; set; }
|
||||
public string CameraModel { get; set; }
|
||||
public string Software { get; set; }
|
||||
|
@ -157,7 +157,8 @@ namespace MediaBrowser.Controller.Library
|
||||
/// <param name="introProviders">The intro providers.</param>
|
||||
/// <param name="itemComparers">The item comparers.</param>
|
||||
/// <param name="postscanTasks">The postscan tasks.</param>
|
||||
void AddParts(IEnumerable<IResolverIgnoreRule> rules,
|
||||
void AddParts(
|
||||
IEnumerable<IResolverIgnoreRule> rules,
|
||||
IEnumerable<IItemResolver> resolvers,
|
||||
IEnumerable<IIntroProvider> introProviders,
|
||||
IEnumerable<IBaseItemComparer> itemComparers,
|
||||
@ -349,9 +350,6 @@ namespace MediaBrowser.Controller.Library
|
||||
/// <returns><c>true</c> if [is audio file] [the specified path]; otherwise, <c>false</c>.</returns>
|
||||
bool IsAudioFile(string path);
|
||||
|
||||
bool IsAudioFile(string path, LibraryOptions libraryOptions);
|
||||
bool IsVideoFile(string path, LibraryOptions libraryOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the season number from path.
|
||||
/// </summary>
|
||||
|
@ -1,3 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -316,11 +316,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue)
|
||||
{
|
||||
var size = new ImageDimensions
|
||||
{
|
||||
Width = VideoStream.Width.Value,
|
||||
Height = VideoStream.Height.Value
|
||||
};
|
||||
var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value);
|
||||
|
||||
var newSize = DrawingUtils.Resize(size,
|
||||
BaseRequest.Width ?? 0,
|
||||
@ -346,11 +342,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue)
|
||||
{
|
||||
var size = new ImageDimensions
|
||||
{
|
||||
Width = VideoStream.Width.Value,
|
||||
Height = VideoStream.Height.Value
|
||||
};
|
||||
var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value);
|
||||
|
||||
var newSize = DrawingUtils.Resize(size,
|
||||
BaseRequest.Width ?? 0,
|
||||
|
@ -1,61 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace MediaBrowser.Controller.Providers
|
||||
{
|
||||
public interface IImageEnhancer
|
||||
{
|
||||
/// <summary>
|
||||
/// Return true only if the given image for the given item will be enhanced by this enhancer.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="imageType">Type of the image.</param>
|
||||
/// <returns><c>true</c> if this enhancer will enhance the supplied image for the supplied item, <c>false</c> otherwise</returns>
|
||||
bool Supports(BaseItem item, ImageType imageType);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the priority or order in which this enhancer should be run.
|
||||
/// </summary>
|
||||
/// <value>The priority.</value>
|
||||
MetadataProviderPriority Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Return a key incorporating all configuration information related to this item
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="imageType">Type of the image.</param>
|
||||
/// <returns>Cache key relating to the current state of this item and configuration</returns>
|
||||
string GetConfigurationCacheKey(BaseItem item, ImageType imageType);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of 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>
|
||||
/// <param name="originalImageSize">Size of the original image.</param>
|
||||
/// <returns>ImageSize.</returns>
|
||||
ImageDimensions GetEnhancedImageSize(BaseItem item, ImageType imageType, int imageIndex, ImageDimensions originalImageSize);
|
||||
|
||||
EnhancedImageInfo GetEnhancedImageInfo(BaseItem item, string inputFile, ImageType imageType, int imageIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Enhances the image async.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="inputFile">The input file.</param>
|
||||
/// <param name="outputFile">The output file.</param>
|
||||
/// <param name="imageType">Type of the image.</param>
|
||||
/// <param name="imageIndex">Index of the image.</param>
|
||||
/// <returns>Task{Image}.</returns>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
Task EnhanceImageAsync(BaseItem item, string inputFile, string outputFile, ImageType imageType, int imageIndex);
|
||||
}
|
||||
|
||||
public class EnhancedImageInfo
|
||||
{
|
||||
public bool RequiresTransparency { get; set; }
|
||||
}
|
||||
}
|
@ -1,39 +1,46 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1600
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
namespace MediaBrowser.Model.Drawing
|
||||
{
|
||||
/// <summary>
|
||||
/// Struct ImageDimensions.
|
||||
/// </summary>
|
||||
public struct ImageDimensions
|
||||
public readonly struct ImageDimensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the height.
|
||||
/// </summary>
|
||||
/// <value>The height.</value>
|
||||
public int Height { get; set; }
|
||||
public ImageDimensions(int width, int height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width.
|
||||
/// Gets the height.
|
||||
/// </summary>
|
||||
/// <value>The height.</value>
|
||||
public int Height { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the width.
|
||||
/// </summary>
|
||||
/// <value>The width.</value>
|
||||
public int Width { get; set; }
|
||||
public int Width { get; }
|
||||
|
||||
public bool Equals(ImageDimensions size)
|
||||
{
|
||||
return Width.Equals(size.Width) && Height.Equals(size.Height);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}-{1}", Width, Height);
|
||||
}
|
||||
|
||||
public ImageDimensions(int width, int height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}-{1}",
|
||||
Width,
|
||||
Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,5 +30,11 @@ namespace MediaBrowser.Model.Querying
|
||||
{
|
||||
Items = Array.Empty<T>();
|
||||
}
|
||||
|
||||
public QueryResult(IReadOnlyList<T> items)
|
||||
{
|
||||
Items = items;
|
||||
TotalRecordCount = items.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
<Rule Id="SA1202" Action="Info" />
|
||||
<!-- disable warning SA1204: Static members must appear before non-static members -->
|
||||
<Rule Id="SA1204" Action="Info" />
|
||||
<!-- disable warning SA1404: Code analysis suppression should have justification -->
|
||||
<Rule Id="SA1404" Action="Info" />
|
||||
|
||||
<!-- disable warning SA1009: Closing parenthesis should be followed by a space. -->
|
||||
<Rule Id="SA1009" Action="None" />
|
||||
|
@ -6,388 +6,78 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
{
|
||||
public class EpisodeNumberTests
|
||||
{
|
||||
[Fact]
|
||||
public void TestEpisodeNumber1()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/S02E03 blah.avi"));
|
||||
}
|
||||
private readonly NamingOptions _namingOptions = new NamingOptions();
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber40()
|
||||
[Theory]
|
||||
[InlineData("Watchmen (2019)/Watchmen 1x03 [WEBDL-720p][EAC3 5.1][h264][-TBS] - She Was Killed by Space Junk.mkv", 3)]
|
||||
[InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", 22)]
|
||||
[InlineData("Castle Rock 2x01 Que el rio siga su curso [WEB-DL HULU 1080p h264 Dual DD5.1 Subs].mkv", 1)]
|
||||
[InlineData("After Life 1x06 Episodio 6 [WEB-DL NF 1080p h264 Dual DD 5.1 Sub].mkv", 6)]
|
||||
[InlineData("Season 02/S02E03 blah.avi", 3)]
|
||||
[InlineData("Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 02/02x03 - x04 - x15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 1/01x02 blah.avi", 2)]
|
||||
[InlineData("Season 1/S01x02 blah.avi", 2)]
|
||||
[InlineData("Season 1/S01E02 blah.avi", 2)]
|
||||
[InlineData("Season 2/Elementary - 02x03-04-15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 1/S01xE02 blah.avi", 2)]
|
||||
[InlineData("Season 1/seriesname S01E02 blah.avi", 2)]
|
||||
[InlineData("Season 2/Episode - 16.avi", 16)]
|
||||
[InlineData("Season 2/Episode 16.avi", 16)]
|
||||
[InlineData("Season 2/Episode 16 - Some Title.avi", 16)]
|
||||
[InlineData("Season 2/16 Some Title.avi", 16)]
|
||||
[InlineData("Season 2/16 - 12 Some Title.avi", 16)]
|
||||
[InlineData("Season 2/7 - 12 Angry Men.avi", 7)]
|
||||
[InlineData("Season 1/seriesname 01x02 blah.avi", 2)]
|
||||
[InlineData("Season 25/The Simpsons.S25E09.Steal this episode.mp4", 9)]
|
||||
[InlineData("Season 1/seriesname S01x02 blah.avi", 2)]
|
||||
[InlineData("Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 1/seriesname S01xE02 blah.avi", 2)]
|
||||
[InlineData("Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 02/Elementary - 02x03x04x15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 2/02x03-04-15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 02/02x03-E15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 02/Elementary - 02x03-E15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4", 23)]
|
||||
[InlineData("Season 2009/S2009E23-E24-E26 - The Woman.mp4", 23)]
|
||||
[InlineData("Season 2009/2009x02 blah.avi", 2)]
|
||||
[InlineData("Season 2009/S2009x02 blah.avi", 2)]
|
||||
[InlineData("Season 2009/S2009E02 blah.avi", 2)]
|
||||
[InlineData("Season 2009/seriesname 2009x02 blah.avi", 2)]
|
||||
[InlineData("Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 2009/2009x03x04x15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 2009/Elementary - 2009x03-E15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 2009/S2009xE02 blah.avi", 2)]
|
||||
[InlineData("Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4", 23)]
|
||||
[InlineData("Season 2009/seriesname S2009xE02 blah.avi", 2)]
|
||||
[InlineData("Season 2009/2009x03-E15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 2009/seriesname S2009E02 blah.avi", 2)]
|
||||
[InlineData("Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 2009/2009x03 - x04 - x15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 2009/seriesname S2009x02 blah.avi", 2)]
|
||||
[InlineData("Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 2009/Elementary - 2009x03-04-15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 2009/2009x03-04-15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4", 3)]
|
||||
[InlineData("Season 1/02 - blah-02 a.avi", 2)]
|
||||
[InlineData("Season 1/02 - blah.avi", 2)]
|
||||
[InlineData("Season 2/02 - blah 14 blah.avi", 2)]
|
||||
[InlineData("Season 2/02.avi", 2)]
|
||||
[InlineData("Season 2/2. Infestation.avi", 2)]
|
||||
[InlineData("The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH/The Wonder Years s04e07 Christmas Party NTSC PDTV.avi", 7)]
|
||||
[InlineData("Running Man/Running Man S2017E368.mkv", 368)]
|
||||
// TODO: [InlineData("Season 2/16 12 Some Title.avi", 16)]
|
||||
// TODO: [InlineData("/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv", 7)]
|
||||
// TODO: [InlineData("Season 4/Uchuu.Senkan.Yamato.2199.E03.avi", 3)]
|
||||
// TODO: [InlineData("Season 2/7 12 Angry Men.avi", 7)]
|
||||
// TODO: [InlineData("Season 02/02x03x04x15 - Ep Name.mp4", 2)]
|
||||
// TODO: [InlineData("Season 2/[HorribleSubs] Hunter X Hunter - 136 [720p].mkv", 136)]
|
||||
public void GetEpisodeNumberFromFileTest(string path, int? expected)
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber41()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/01x02 blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber42()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/S01x02 blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber43()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/S01E02 blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber44()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber45()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/S01xE02 blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber46()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname S01E02 blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber47()
|
||||
{
|
||||
Assert.Equal(36, GetEpisodeNumberFromFile(@"Season 2/[HorribleSubs] Hunter X Hunter - 136 [720p].mkv"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber52()
|
||||
{
|
||||
Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode - 16.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber53()
|
||||
{
|
||||
Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode 16.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber54()
|
||||
{
|
||||
Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode 16 - Some Title.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber57()
|
||||
{
|
||||
Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/16 Some Title.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber58()
|
||||
{
|
||||
Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/16 - 12 Some Title.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber59()
|
||||
{
|
||||
Assert.Equal(7, GetEpisodeNumberFromFile(@"Season 2/7 - 12 Angry Men.avi"));
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
public void TestEpisodeNumber60()
|
||||
{
|
||||
Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/16 12 Some Title.avi"));
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
public void TestEpisodeNumber61()
|
||||
{
|
||||
Assert.Equal(7, GetEpisodeNumberFromFile(@"Season 2/7 12 Angry Men.avi"));
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
public void TestEpisodeNumber62()
|
||||
{
|
||||
// This is not supported. Expected to fail, although it would be a good one to add support for.
|
||||
Assert.Equal(3, GetEpisodeNumberFromFile(@"Season 4/Uchuu.Senkan.Yamato.2199.E03.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber63()
|
||||
{
|
||||
Assert.Equal(3, GetEpisodeNumberFromFile(@"Season 4/Uchuu.Senkan.Yamato.2199.S04E03.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber64()
|
||||
{
|
||||
Assert.Equal(368, GetEpisodeNumberFromFile(@"Running Man/Running Man S2017E368.mkv"));
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
public void TestEpisodeNumber65()
|
||||
{
|
||||
// Not supported yet
|
||||
Assert.Equal(7, GetEpisodeNumberFromFile(@"/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber30()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
public void TestEpisodeNumber31()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname 01x02 blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber32()
|
||||
{
|
||||
Assert.Equal(9, GetEpisodeNumberFromFile(@"Season 25/The Simpsons.S25E09.Steal this episode.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber33()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname S01x02 blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber34()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber35()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname S01xE02 blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber36()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber37()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber38()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/02x03x04x15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber39()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber20()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/02x03-04-15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber21()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/02x03-E15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber22()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber23()
|
||||
{
|
||||
Assert.Equal(23, GetEpisodeNumberFromFile(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber24()
|
||||
{
|
||||
Assert.Equal(23, GetEpisodeNumberFromFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber25()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/2009x02 blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber26()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/S2009x02 blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber27()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/S2009E02 blah.avi"));
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
public void TestEpisodeNumber28()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname 2009x02 blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber29()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber11()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber12()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-E15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber13()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/S2009xE02 blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber14()
|
||||
{
|
||||
Assert.Equal(23, GetEpisodeNumberFromFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber15()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname S2009xE02 blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber16()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03-E15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber17()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname S2009E02 blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber18()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber19()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03 - x04 - x15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber2()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname S2009x02 blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber3()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber4()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-04-15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber5()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03-04-15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber6()
|
||||
{
|
||||
Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber7()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/02 - blah-02 a.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber8()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/02 - blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber9()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2/02 - blah 14 blah.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber10()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2/02.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber48()
|
||||
{
|
||||
Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2/2. Infestation.avi"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEpisodeNumber49()
|
||||
{
|
||||
Assert.Equal(7, GetEpisodeNumberFromFile(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH/The Wonder Years s04e07 Christmas Party NTSC PDTV.avi"));
|
||||
}
|
||||
|
||||
private int? GetEpisodeNumberFromFile(string path)
|
||||
{
|
||||
var options = new NamingOptions();
|
||||
|
||||
var result = new EpisodePathParser(options)
|
||||
var result = new EpisodePathParser(_namingOptions)
|
||||
.Parse(path, false);
|
||||
|
||||
return result.EpisodeNumber;
|
||||
Assert.Equal(expected, result.EpisodeNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,21 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
{
|
||||
public class SeasonNumberTests
|
||||
{
|
||||
private readonly NamingOptions _namingOptions = new NamingOptions();
|
||||
|
||||
[Theory]
|
||||
[InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", 25)]
|
||||
public void GetSeasonNumberFromEpisodeFileTest(string path, int? expected)
|
||||
{
|
||||
var result = new EpisodeResolver(_namingOptions)
|
||||
.Resolve(path, false);
|
||||
|
||||
Assert.Equal(expected, result.SeasonNumber);
|
||||
}
|
||||
|
||||
private int? GetSeasonNumberFromEpisodeFile(string path)
|
||||
{
|
||||
var options = new NamingOptions();
|
||||
|
||||
var result = new EpisodeResolver(options)
|
||||
var result = new EpisodeResolver(_namingOptions)
|
||||
.Resolve(path, false);
|
||||
|
||||
return result.SeasonNumber;
|
||||
|
Loading…
Reference in New Issue
Block a user