Merge pull request 'pgsql_conversion' (#5) from pgsql_conversion into main
Reviewed-on: #5
This commit is contained in:
+31
-7
@@ -1,13 +1,38 @@
|
||||
# EditorConfig for MediaBrowser.Model# https://EditorConfig.orgroot = true[*]charset = utf-8insert_final_newline = truetrim_trailing_whitespace = true[*.cs]indent_size = 4indent_style = spacedotnet_sort_system_directives_first = truedotnet_separate_import_directive_groups = false# StyleCop Analyzer Rules - Disabled for project consistencydotnet_diagnostic.SA1101.severity = nonedotnet_diagnostic.SA1309.severity = nonedotnet_diagnostic.SA1204.severity = nonedotnet_diagnostic.SA1202.severity = nonedotnet_diagnostic.SA1135.severity = nonedotnet_diagnostic.SA1600.severity = suggestion# StyleCop Analyzer Rules - Keep enableddotnet_diagnostic.SA1413.severity = warningdotnet_diagnostic.SA1515.severity = warningdotnet_diagnostic.SA1518.severity = warning-
|
||||
# EditorConfig for MediaBrowser.Model
|
||||
# https://EditorConfig.org
|
||||
|
||||
root = true
|
||||
|
||||
# StyleCop Analyzer Rules - Additional suppressions for Emby.Naming
|
||||
dotnet_diagnostic.SA1200.severity = none
|
||||
dotnet_diagnostic.SA1633.severity = none
|
||||
[*]
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Reduce severity for trailing commas and comments
|
||||
[*.cs]
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
dotnet_sort_system_directives_first = true
|
||||
dotnet_separate_import_directive_groups = false
|
||||
|
||||
# StyleCop Analyzer Rules - Disabled for project consistency
|
||||
dotnet_diagnostic.SA1101.severity = none
|
||||
dotnet_diagnostic.SA1309.severity = none
|
||||
dotnet_diagnostic.SA1204.severity = none
|
||||
dotnet_diagnostic.SA1202.severity = none
|
||||
dotnet_diagnostic.SA1135.severity = none
|
||||
dotnet_diagnostic.SA1600.severity = none
|
||||
dotnet_diagnostic.SA1601.severity = none
|
||||
|
||||
# StyleCop Analyzer Rules - Reduced severity
|
||||
dotnet_diagnostic.SA1413.severity = silent
|
||||
dotnet_diagnostic.SA1515.severity = silent
|
||||
dotnet_diagnostic.SA1512.severity = silent
|
||||
dotnet_diagnostic.SA1518.severity = warning
|
||||
dotnet_diagnostic.SA1648.severity = none
|
||||
|
||||
# StyleCop Analyzer Rules - Additional suppressions
|
||||
dotnet_diagnostic.SA1200.severity = none
|
||||
dotnet_diagnostic.SA1633.severity = none
|
||||
|
||||
# IDE Rules - Suppress non-critical suggestions
|
||||
dotnet_diagnostic.IDE0008.severity = silent
|
||||
@@ -17,7 +42,7 @@ dotnet_diagnostic.IDE0051.severity = warning
|
||||
dotnet_diagnostic.IDE0055.severity = warning
|
||||
dotnet_diagnostic.IDE0057.severity = silent
|
||||
dotnet_diagnostic.IDE0058.severity = silent
|
||||
dotnet_diagnostic.IDE0065.severity = silent
|
||||
dotnet_diagnostic.IDE0065.severity = none
|
||||
dotnet_diagnostic.IDE0078.severity = silent
|
||||
dotnet_diagnostic.IDE0090.severity = silent
|
||||
dotnet_diagnostic.IDE0160.severity = silent
|
||||
@@ -33,4 +58,3 @@ dotnet_diagnostic.CA1310.severity = warning
|
||||
|
||||
# C# Code Style - Using directive placement
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
<PropertyGroup>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsNotAsErrors>NU1902;NU1903</WarningsNotAsErrors>
|
||||
<!-- Suppress IDE0065 globally for all projects -->
|
||||
<NoWarn>$(NoWarn);IDE0065</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
<!-- Don't enforce code style rules as build errors in Debug -->
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors></WarningsAsErrors>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<NoWarn>$(NoWarn);IDE0065</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
||||
|
||||
+131
-144
@@ -2,178 +2,165 @@
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace Emby.Photos;
|
||||
#pragma warning disable SA1309 // Variables should not begin with underscore
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TagLib;
|
||||
using TagLib.IFD;
|
||||
using TagLib.IFD.Entries;
|
||||
using TagLib.IFD.Tags;
|
||||
|
||||
/// <summary>
|
||||
/// Metadata provider for photos.
|
||||
/// </summary>
|
||||
public class PhotoProvider : ICustomMetadataProvider<Photo>, IForcedProvider, IHasItemChangeMonitor
|
||||
namespace Emby.Photos
|
||||
{
|
||||
private readonly ILogger<PhotoProvider> _logger;
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
|
||||
// Other extensions might cause taglib to hang
|
||||
private readonly string[] _includeExtensions = [".jpg", ".jpeg", ".png", ".tiff", ".cr2", ".webp", ".avif"];
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TagLib;
|
||||
using TagLib.IFD;
|
||||
using TagLib.IFD.Entries;
|
||||
using TagLib.IFD.Tags;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PhotoProvider" /> class.
|
||||
/// Metadata provider for photos.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="imageProcessor">The image processor.</param>
|
||||
public PhotoProvider(ILogger<PhotoProvider> logger, IImageProcessor imageProcessor)
|
||||
public class PhotoProvider(ILogger<PhotoProvider> logger, IImageProcessor imageProcessor) : ICustomMetadataProvider<Photo>, IForcedProvider, IHasItemChangeMonitor
|
||||
{
|
||||
_logger = logger;
|
||||
_imageProcessor = imageProcessor;
|
||||
}
|
||||
// Other extensions might cause taglib to hang
|
||||
private readonly string[] _includeExtensions = [".jpg", ".jpeg", ".png", ".tiff", ".cr2", ".webp", ".avif"];
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Embedded Information";
|
||||
/// <inheritdoc />
|
||||
public string Name => "Embedded Information";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
|
||||
{
|
||||
if (item.IsFileProtocol)
|
||||
/// <inheritdoc />
|
||||
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
|
||||
{
|
||||
var file = directoryService.GetFile(item.Path);
|
||||
return file is not null && item.HasChanged(file.LastWriteTimeUtc);
|
||||
if (item.IsFileProtocol)
|
||||
{
|
||||
FileSystemMetadata? file = directoryService.GetFile(item.Path);
|
||||
return file is not null && item.HasChanged(file.LastWriteTimeUtc);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ItemUpdateType> FetchAsync(Photo item, MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
item.SetImagePath(ImageType.Primary, item.Path);
|
||||
|
||||
// Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs
|
||||
if (_includeExtensions.Contains(Path.GetExtension(item.Path.AsSpan()), StringComparison.OrdinalIgnoreCase))
|
||||
/// <inheritdoc />
|
||||
public Task<ItemUpdateType> FetchAsync(Photo item, MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var file = TagLib.File.Create(item.Path);
|
||||
if (file.GetTag(TagTypes.TiffIFD) is IFDTag tag)
|
||||
{
|
||||
var structure = tag.Structure;
|
||||
if (structure?.GetEntry(0, (ushort)IFDEntryTag.ExifIFD) is SubIFDEntry exif)
|
||||
{
|
||||
var exifStructure = exif.Structure;
|
||||
if (exifStructure is not null)
|
||||
{
|
||||
if (exifStructure.GetEntry(0, (ushort)ExifEntryTag.ApertureValue) is RationalIFDEntry apertureEntry)
|
||||
{
|
||||
item.Aperture = (double)apertureEntry.Value.Numerator / apertureEntry.Value.Denominator;
|
||||
}
|
||||
item.SetImagePath(ImageType.Primary, item.Path);
|
||||
|
||||
if (exifStructure.GetEntry(0, (ushort)ExifEntryTag.ShutterSpeedValue) is RationalIFDEntry shutterSpeedEntry)
|
||||
// Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs
|
||||
if (this._includeExtensions.Contains(Path.GetExtension(item.Path.AsSpan()), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
using TagLib.File file = TagLib.File.Create(item.Path);
|
||||
if (file.GetTag(TagTypes.TiffIFD) is IFDTag tag)
|
||||
{
|
||||
IFDStructure? structure = tag.Structure;
|
||||
if (structure?.GetEntry(0, (ushort)IFDEntryTag.ExifIFD) is SubIFDEntry exif)
|
||||
{
|
||||
IFDStructure? exifStructure = exif.Structure;
|
||||
if (exifStructure is not null)
|
||||
{
|
||||
item.ShutterSpeed = (double)shutterSpeedEntry.Value.Numerator / shutterSpeedEntry.Value.Denominator;
|
||||
if (exifStructure.GetEntry(0, (ushort)ExifEntryTag.ApertureValue) is RationalIFDEntry apertureEntry)
|
||||
{
|
||||
item.Aperture = (double)apertureEntry.Value.Numerator / apertureEntry.Value.Denominator;
|
||||
}
|
||||
|
||||
if (exifStructure.GetEntry(0, (ushort)ExifEntryTag.ShutterSpeedValue) is RationalIFDEntry shutterSpeedEntry)
|
||||
{
|
||||
item.ShutterSpeed = (double)shutterSpeedEntry.Value.Numerator / shutterSpeedEntry.Value.Denominator;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (file is TagLib.Image.File image)
|
||||
if (file is TagLib.Image.File image)
|
||||
{
|
||||
item.CameraMake = image.ImageTag.Make;
|
||||
item.CameraModel = image.ImageTag.Model;
|
||||
|
||||
item.Width = image.Properties.PhotoWidth;
|
||||
item.Height = image.Properties.PhotoHeight;
|
||||
|
||||
item.CommunityRating = image.ImageTag.Rating;
|
||||
|
||||
item.Overview = image.ImageTag.Comment;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(image.ImageTag.Title)
|
||||
&& !item.LockedFields.Contains(MetadataField.Name))
|
||||
{
|
||||
item.Name = image.ImageTag.Title;
|
||||
}
|
||||
|
||||
DateTime? dateTaken = image.ImageTag.DateTime;
|
||||
if (dateTaken.HasValue)
|
||||
{
|
||||
item.DateCreated = dateTaken.Value.ToUniversalTime();
|
||||
item.PremiereDate = dateTaken.Value;
|
||||
item.ProductionYear = dateTaken.Value.Year;
|
||||
}
|
||||
|
||||
item.Genres = image.ImageTag.Genres;
|
||||
item.Tags = image.ImageTag.Keywords;
|
||||
item.Software = image.ImageTag.Software;
|
||||
|
||||
if (image.ImageTag.Orientation == TagLib.Image.ImageOrientation.None)
|
||||
{
|
||||
item.Orientation = null;
|
||||
}
|
||||
else if (Enum.TryParse(image.ImageTag.Orientation.ToString(), true, out ImageOrientation orientation))
|
||||
{
|
||||
item.Orientation = orientation;
|
||||
}
|
||||
|
||||
item.ExposureTime = image.ImageTag.ExposureTime;
|
||||
item.FocalLength = image.ImageTag.FocalLength;
|
||||
|
||||
item.Latitude = image.ImageTag.Latitude;
|
||||
item.Longitude = image.ImageTag.Longitude;
|
||||
item.Altitude = image.ImageTag.Altitude;
|
||||
|
||||
item.IsoSpeedRating = image.ImageTag.ISOSpeedRatings.HasValue
|
||||
? Convert.ToInt32(image.ImageTag.ISOSpeedRatings.Value)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
item.CameraMake = image.ImageTag.Make;
|
||||
item.CameraModel = image.ImageTag.Model;
|
||||
|
||||
item.Width = image.Properties.PhotoWidth;
|
||||
item.Height = image.Properties.PhotoHeight;
|
||||
|
||||
item.CommunityRating = image.ImageTag.Rating;
|
||||
|
||||
item.Overview = image.ImageTag.Comment;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(image.ImageTag.Title)
|
||||
&& !item.LockedFields.Contains(MetadataField.Name))
|
||||
{
|
||||
item.Name = image.ImageTag.Title;
|
||||
}
|
||||
|
||||
var dateTaken = image.ImageTag.DateTime;
|
||||
if (dateTaken.HasValue)
|
||||
{
|
||||
item.DateCreated = dateTaken.Value.ToUniversalTime();
|
||||
item.PremiereDate = dateTaken.Value;
|
||||
item.ProductionYear = dateTaken.Value.Year;
|
||||
}
|
||||
|
||||
item.Genres = image.ImageTag.Genres;
|
||||
item.Tags = image.ImageTag.Keywords;
|
||||
item.Software = image.ImageTag.Software;
|
||||
|
||||
if (image.ImageTag.Orientation == TagLib.Image.ImageOrientation.None)
|
||||
{
|
||||
item.Orientation = null;
|
||||
}
|
||||
else if (Enum.TryParse(image.ImageTag.Orientation.ToString(), true, out ImageOrientation orientation))
|
||||
{
|
||||
item.Orientation = orientation;
|
||||
}
|
||||
|
||||
item.ExposureTime = image.ImageTag.ExposureTime;
|
||||
item.FocalLength = image.ImageTag.FocalLength;
|
||||
|
||||
item.Latitude = image.ImageTag.Latitude;
|
||||
item.Longitude = image.ImageTag.Longitude;
|
||||
item.Altitude = image.ImageTag.Altitude;
|
||||
|
||||
if (image.ImageTag.ISOSpeedRatings.HasValue)
|
||||
{
|
||||
item.IsoSpeedRating = Convert.ToInt32(image.ImageTag.ISOSpeedRatings.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.IsoSpeedRating = null;
|
||||
}
|
||||
logger.LogError(ex, "Image Provider - Error reading image tag for {0}", item.Path);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
if (item.Width <= 0 || item.Height <= 0)
|
||||
{
|
||||
_logger.LogError(ex, "Image Provider - Error reading image tag for {0}", item.Path);
|
||||
}
|
||||
}
|
||||
ItemImageInfo img = item.GetImageInfo(ImageType.Primary, 0);
|
||||
|
||||
if (item.Width <= 0 || item.Height <= 0)
|
||||
{
|
||||
var img = item.GetImageInfo(ImageType.Primary, 0);
|
||||
|
||||
try
|
||||
{
|
||||
var size = _imageProcessor.GetImageDimensions(item, img);
|
||||
|
||||
if (size.Width > 0 && size.Height > 0)
|
||||
try
|
||||
{
|
||||
item.Width = size.Width;
|
||||
item.Height = size.Height;
|
||||
ImageDimensions size = imageProcessor.GetImageDimensions(item, img);
|
||||
|
||||
if (size.Width > 0 && size.Height > 0)
|
||||
{
|
||||
item.Width = size.Width;
|
||||
item.Height = size.Height;
|
||||
}
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// format not supported
|
||||
}
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// format not supported
|
||||
}
|
||||
}
|
||||
|
||||
const ItemUpdateType Result = ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataImport;
|
||||
return Task.FromResult(Result);
|
||||
const ItemUpdateType Result = ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataImport;
|
||||
return Task.FromResult(Result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "DaMoU2oavX0=",
|
||||
"dgSpecHash": "leFWYb0Dcsk=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\Emby.Photos\\Emby.Photos.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "CU3WcVn0qXE=",
|
||||
"dgSpecHash": "ZVA5ePVHqjg=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\Emby.Server.Implementations\\Emby.Server.Implementations.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
# How to Ignore IDE0065 Error Message
|
||||
|
||||
## What is IDE0065?
|
||||
|
||||
**IDE0065**: "Using directives must be placed outside of a namespace declaration"
|
||||
|
||||
This is a code style warning about where `using` statements should be placed - inside or outside the namespace.
|
||||
|
||||
## Solutions Implemented ✅
|
||||
|
||||
### Solution 1: .editorconfig (Applied) ✅
|
||||
|
||||
Updated `.editorconfig` to completely disable IDE0065:
|
||||
|
||||
```ini
|
||||
# IDE Rules - Suppress non-critical suggestions
|
||||
dotnet_diagnostic.IDE0065.severity = none
|
||||
```
|
||||
|
||||
**Severity Levels:**
|
||||
- `none` = Completely disabled (doesn't appear anywhere)
|
||||
- `silent` = Hidden in IDE, but may show in build
|
||||
- `suggestion` = Shows as hint in IDE
|
||||
- `warning` = Shows as warning
|
||||
- `error` = Blocks build
|
||||
|
||||
### Solution 2: Project File (Applied) ✅
|
||||
|
||||
Added to `Emby.Naming.csproj`:
|
||||
|
||||
```xml
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<NoWarn>$(NoWarn);IDE0065</NoWarn>
|
||||
</PropertyGroup>
|
||||
```
|
||||
|
||||
This ensures IDE0065 is completely suppressed during build.
|
||||
|
||||
## Additional Methods (If Needed)
|
||||
|
||||
### Method 3: Global Suppression File
|
||||
|
||||
Create a file `GlobalSuppressions.cs` in your project:
|
||||
|
||||
```csharp
|
||||
// <copyright file="GlobalSuppressions.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Style", "IDE0065:Misplaced using directive", Justification = "Project style preference")]
|
||||
```
|
||||
|
||||
### Method 4: Suppress for All Configurations
|
||||
|
||||
If you want to suppress IDE0065 for **both Debug and Release**, add this to your `.csproj`:
|
||||
|
||||
```xml
|
||||
<PropertyGroup>
|
||||
<NoWarn>$(NoWarn);IDE0065</NoWarn>
|
||||
</PropertyGroup>
|
||||
```
|
||||
|
||||
### Method 5: Visual Studio Settings (Local Only)
|
||||
|
||||
If you only want to hide it in **your IDE** (doesn't affect builds):
|
||||
|
||||
1. Open Visual Studio
|
||||
2. **Tools** → **Options**
|
||||
3. **Text Editor** → **C#** → **Code Style** → **Formatting**
|
||||
4. Under "Usings", set your preference
|
||||
5. Or go to **Options** → **Environment** → **Task List** and filter warnings
|
||||
|
||||
**Note:** This only affects your local IDE, not team builds.
|
||||
|
||||
## Verify the Changes
|
||||
|
||||
### Check in Visual Studio:
|
||||
1. Restart Visual Studio (or reload solution)
|
||||
2. Build the project
|
||||
3. IDE0065 should no longer appear in Error List
|
||||
|
||||
### Check in Command Line:
|
||||
```powershell
|
||||
# Clean and rebuild
|
||||
dotnet clean Emby.Naming\Emby.Naming.csproj
|
||||
dotnet build Emby.Naming\Emby.Naming.csproj
|
||||
|
||||
# IDE0065 should not appear in output
|
||||
```
|
||||
|
||||
## What the Changes Mean
|
||||
|
||||
### For Your IDE:
|
||||
- ✅ IDE0065 will **not show** in the Error List
|
||||
- ✅ No underlines or suggestions in the code editor
|
||||
- ✅ Quick Actions (Ctrl+.) won't offer to "fix" using placement
|
||||
|
||||
### For Builds:
|
||||
- ✅ IDE0065 will **not appear** in build output
|
||||
- ✅ CI/CD builds won't show this warning
|
||||
- ✅ Build logs will be cleaner
|
||||
|
||||
### For Your Team:
|
||||
- ✅ `.editorconfig` is checked into source control
|
||||
- ✅ All developers will have the same suppression
|
||||
- ✅ Consistent experience across the team
|
||||
|
||||
## Why Both Methods?
|
||||
|
||||
We applied **both** methods for maximum coverage:
|
||||
|
||||
1. **`.editorconfig`** → Suppresses in IDE and most build scenarios
|
||||
2. **`<NoWarn>`** → Guarantees suppression during MSBuild/command-line builds
|
||||
|
||||
This ensures IDE0065 is completely eliminated everywhere!
|
||||
|
||||
## Other Common IDE Errors to Suppress
|
||||
|
||||
If you want to suppress other common warnings, add them to `<NoWarn>`:
|
||||
|
||||
```xml
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<NoWarn>$(NoWarn);IDE0065;IDE0008;IDE0058;SA1309;SA1101</NoWarn>
|
||||
</PropertyGroup>
|
||||
```
|
||||
|
||||
**Common Ones:**
|
||||
- `IDE0008` = Use explicit type instead of 'var'
|
||||
- `IDE0058` = Expression value is never used
|
||||
- `IDE0065` = Using directive placement
|
||||
- `SA1309` = Field names should not begin with underscore
|
||||
- `SA1101` = Prefix local calls with 'this'
|
||||
- `SA1200` = Using directives placement (StyleCop version)
|
||||
|
||||
## Testing
|
||||
|
||||
After making these changes:
|
||||
|
||||
```powershell
|
||||
# 1. Clean everything
|
||||
dotnet clean
|
||||
|
||||
# 2. Rebuild
|
||||
dotnet build Emby.Naming\Emby.Naming.csproj
|
||||
|
||||
# 3. Check for IDE0065 in output (should be gone!)
|
||||
```
|
||||
|
||||
Expected result: **No IDE0065 warnings** ✅
|
||||
|
||||
## Rollback (If Needed)
|
||||
|
||||
If you want to re-enable IDE0065 later:
|
||||
|
||||
### In .editorconfig:
|
||||
```ini
|
||||
dotnet_diagnostic.IDE0065.severity = suggestion
|
||||
```
|
||||
|
||||
### In .csproj:
|
||||
Remove `IDE0065` from the `<NoWarn>` list.
|
||||
|
||||
## Summary
|
||||
|
||||
✅ **Both methods applied!**
|
||||
- `.editorconfig` updated: `IDE0065.severity = none`
|
||||
- `Emby.Naming.csproj` updated: `<NoWarn>$(NoWarn);IDE0065</NoWarn>`
|
||||
|
||||
**Result:** IDE0065 is now completely suppressed in:
|
||||
- ✅ Visual Studio IDE
|
||||
- ✅ Command-line builds
|
||||
- ✅ MSBuild
|
||||
- ✅ CI/CD pipelines
|
||||
|
||||
You should no longer see IDE0065 anywhere! 🎉
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "sEpjYe2J48k=",
|
||||
"dgSpecHash": "4CpeL+8YFHs=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\Jellyfin.Api\\Jellyfin.Api.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "2uqnqkg9VpI=",
|
||||
"dgSpecHash": "8rGyZK2RqJs=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\Jellyfin.Server.Implementations\\Jellyfin.Server.Implementations.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "uvfyRwbCsEk=",
|
||||
"dgSpecHash": "WNAhlXOd1t0=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\Jellyfin.Server\\Jellyfin.Server.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# EditorConfig for MediaBrowser.Controller
|
||||
# https://EditorConfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*.cs]
|
||||
# Disable StyleCop rules that are not followed in this legacy project
|
||||
dotnet_diagnostic.SA1101.severity = none
|
||||
dotnet_diagnostic.SA1200.severity = none
|
||||
dotnet_diagnostic.SA1202.severity = none
|
||||
dotnet_diagnostic.SA1204.severity = none
|
||||
dotnet_diagnostic.SA1309.severity = none
|
||||
dotnet_diagnostic.SA1413.severity = none
|
||||
dotnet_diagnostic.SA1515.severity = none
|
||||
dotnet_diagnostic.SA1512.severity = none
|
||||
dotnet_diagnostic.SA1600.severity = none
|
||||
dotnet_diagnostic.SA1601.severity = none
|
||||
dotnet_diagnostic.SA1602.severity = none
|
||||
dotnet_diagnostic.SA1128.severity = none
|
||||
dotnet_diagnostic.SA1009.severity = none
|
||||
dotnet_diagnostic.SA1108.severity = none
|
||||
|
||||
# Disable IDisposableAnalyzers rules
|
||||
dotnet_diagnostic.IDISP001.severity = none
|
||||
dotnet_diagnostic.IDISP003.severity = none
|
||||
dotnet_diagnostic.IDISP007.severity = none
|
||||
dotnet_diagnostic.IDISP008.severity = none
|
||||
@@ -23,7 +23,7 @@ namespace MediaBrowser.Controller.BaseItemManager
|
||||
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
||||
public BaseItemManager(IServerConfigurationManager serverConfigurationManager)
|
||||
{
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
this._serverConfigurationManager = serverConfigurationManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -46,7 +46,7 @@ namespace MediaBrowser.Controller.BaseItemManager
|
||||
return libraryTypeOptions.MetadataFetchers.Contains(name, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
var itemConfig = _serverConfigurationManager.GetMetadataOptionsForType(baseItem.GetType().Name);
|
||||
var itemConfig = this._serverConfigurationManager.GetMetadataOptionsForType(baseItem.GetType().Name);
|
||||
return itemConfig is null || !itemConfig.DisabledMetadataFetchers.Contains(name, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace MediaBrowser.Controller.BaseItemManager
|
||||
return libraryTypeOptions.ImageFetchers.Contains(name, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
var itemConfig = _serverConfigurationManager.GetMetadataOptionsForType(baseItem.GetType().Name);
|
||||
var itemConfig = this._serverConfigurationManager.GetMetadataOptionsForType(baseItem.GetType().Name);
|
||||
return itemConfig is null || !itemConfig.DisabledImageFetchers.Contains(name, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,18 @@ namespace MediaBrowser.Controller.Channels
|
||||
[JsonIgnore]
|
||||
public override SourceType SourceType => SourceType.Channel;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this channel is visible to the specified user.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <param name="skipAllowedTagsCheck">Whether to skip the allowed tags check.</param>
|
||||
/// <returns>True if visible; otherwise, false.</returns>
|
||||
public override bool IsVisible(User user, bool skipAllowedTagsCheck = false)
|
||||
{
|
||||
var blockedChannelsPreference = user.GetPreferenceValues<Guid>(PreferenceKind.BlockedChannels);
|
||||
if (blockedChannelsPreference.Length != 0)
|
||||
{
|
||||
if (blockedChannelsPreference.Contains(Id))
|
||||
if (blockedChannelsPreference.Contains(this.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -40,7 +46,7 @@ namespace MediaBrowser.Controller.Channels
|
||||
else
|
||||
{
|
||||
if (!user.HasPermission(PermissionKind.EnableAllChannels)
|
||||
&& !user.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels).Contains(Id))
|
||||
&& !user.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels).Contains(this.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -49,12 +55,17 @@ namespace MediaBrowser.Controller.Channels
|
||||
return base.IsVisible(user, skipAllowedTagsCheck);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets items from the channel.
|
||||
/// </summary>
|
||||
/// <param name="query">The items query.</param>
|
||||
/// <returns>The query result.</returns>
|
||||
protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
|
||||
{
|
||||
try
|
||||
{
|
||||
query.Parent = this;
|
||||
query.ChannelIds = new Guid[] { Id };
|
||||
query.ChannelIds = new Guid[] { this.Id };
|
||||
|
||||
// Don't blow up here because it could cause parent screens with other content to fail
|
||||
return ChannelManager.GetChannelItemsInternal(query, new Progress<double>(), CancellationToken.None).GetAwaiter().GetResult();
|
||||
@@ -66,21 +77,42 @@ namespace MediaBrowser.Controller.Channels
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the internal metadata path for this channel.
|
||||
/// </summary>
|
||||
/// <param name="basePath">The base path.</param>
|
||||
/// <returns>The internal metadata path.</returns>
|
||||
protected override string GetInternalMetadataPath(string basePath)
|
||||
{
|
||||
return GetInternalMetadataPath(basePath, Id);
|
||||
return GetInternalMetadataPath(basePath, this.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the internal metadata path for a channel with the specified ID.
|
||||
/// </summary>
|
||||
/// <param name="basePath">The base path.</param>
|
||||
/// <param name="id">The channel ID.</param>
|
||||
/// <returns>The internal metadata path.</returns>
|
||||
public static string GetInternalMetadataPath(string basePath, Guid id)
|
||||
{
|
||||
return System.IO.Path.Combine(basePath, "channels", id.ToString("N", CultureInfo.InvariantCulture), "metadata");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this channel can be deleted.
|
||||
/// </summary>
|
||||
/// <returns>True if deletable; otherwise, false.</returns>
|
||||
public override bool CanDelete()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the channel item is visible to the specified user.
|
||||
/// </summary>
|
||||
/// <param name="channelItem">The channel item.</param>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>True if visible; otherwise, false.</returns>
|
||||
internal static bool IsChannelVisible(BaseItem channelItem, User user)
|
||||
{
|
||||
var channel = ChannelManager.GetChannel(channelItem.ChannelId.ToString(string.Empty));
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||
|
||||
public SongInfo GetLookupInfo()
|
||||
{
|
||||
var info = GetItemLookupInfo<SongInfo>();
|
||||
var info = this.GetItemLookupInfo<SongInfo>();
|
||||
|
||||
info.AlbumArtists = AlbumArtists;
|
||||
info.Album = Album;
|
||||
|
||||
@@ -31,36 +31,64 @@ namespace MediaBrowser.Controller.Entities
|
||||
[JsonIgnore]
|
||||
public Guid SeriesId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Finds the series sort name.
|
||||
/// </summary>
|
||||
/// <returns>The series sort name.</returns>
|
||||
public string FindSeriesSortName()
|
||||
{
|
||||
return SeriesName;
|
||||
return this.SeriesName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the series name.
|
||||
/// </summary>
|
||||
/// <returns>The series name.</returns>
|
||||
public string FindSeriesName()
|
||||
{
|
||||
return SeriesName;
|
||||
return this.SeriesName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the series presentation unique key.
|
||||
/// </summary>
|
||||
/// <returns>The series presentation unique key.</returns>
|
||||
public string FindSeriesPresentationUniqueKey()
|
||||
{
|
||||
return SeriesPresentationUniqueKey;
|
||||
return this.SeriesPresentationUniqueKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default primary image aspect ratio.
|
||||
/// </summary>
|
||||
/// <returns>The default primary image aspect ratio.</returns>
|
||||
public override double GetDefaultPrimaryImageAspectRatio()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the series identifier.
|
||||
/// </summary>
|
||||
/// <returns>The series identifier.</returns>
|
||||
public Guid FindSeriesId()
|
||||
{
|
||||
return SeriesId;
|
||||
return this.SeriesId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this audiobook can be downloaded.
|
||||
/// </summary>
|
||||
/// <returns>True if downloadable; otherwise, false.</returns>
|
||||
public override bool CanDownload()
|
||||
{
|
||||
return IsFileProtocol;
|
||||
return this.IsFileProtocol;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unrated type for blocking purposes.
|
||||
/// </summary>
|
||||
/// <returns>The unrated item type.</returns>
|
||||
public override UnratedItem GetBlockUnratedType()
|
||||
{
|
||||
return UnratedItem.Book;
|
||||
|
||||
@@ -7263,6 +7263,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
return inputModifier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches media source information to the encoding job state.
|
||||
/// </summary>
|
||||
/// <param name="state">The encoding job state.</param>
|
||||
/// <param name="encodingOptions">The encoding options.</param>
|
||||
/// <param name="mediaSource">The media source information.</param>
|
||||
/// <param name="requestedUrl">The requested URL.</param>
|
||||
public void AttachMediaSourceInfo(
|
||||
EncodingJobInfo state,
|
||||
EncodingOptions encodingOptions,
|
||||
@@ -7327,26 +7334,26 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
requestedUrl = "test." + videoRequest.Container;
|
||||
}
|
||||
|
||||
videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
|
||||
videoRequest.VideoCodec = this.InferVideoCodec(requestedUrl);
|
||||
}
|
||||
|
||||
state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
|
||||
state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false);
|
||||
state.VideoStream = this.GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
|
||||
state.SubtitleStream = this.GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false);
|
||||
state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
|
||||
state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
|
||||
state.AudioStream = this.GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
|
||||
|
||||
if (state.SubtitleStream is not null && !state.SubtitleStream.IsExternal)
|
||||
{
|
||||
state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal).ToList().IndexOf(state.SubtitleStream);
|
||||
}
|
||||
|
||||
EnforceResolutionLimit(state);
|
||||
this.EnforceResolutionLimit(state);
|
||||
|
||||
NormalizeSubtitleEmbed(state);
|
||||
this.NormalizeSubtitleEmbed(state);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
|
||||
state.AudioStream = this.GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
|
||||
}
|
||||
|
||||
state.MediaSource = mediaSource;
|
||||
@@ -7357,11 +7364,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
var supportedAudioCodecsList = supportedAudioCodecs.ToList();
|
||||
|
||||
ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
|
||||
this.ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
|
||||
|
||||
state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray();
|
||||
|
||||
request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(_mediaEncoder.CanEncodeToAudioCodec)
|
||||
request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(this._mediaEncoder.CanEncodeToAudioCodec)
|
||||
?? state.SupportedAudioCodecs.FirstOrDefault();
|
||||
}
|
||||
|
||||
@@ -7370,7 +7377,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
var supportedVideoCodecsList = supportedVideoCodecs.ToList();
|
||||
|
||||
ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions);
|
||||
this.ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions);
|
||||
|
||||
state.SupportedVideoCodecs = supportedVideoCodecsList.ToArray();
|
||||
|
||||
@@ -7791,6 +7798,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
|| (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the video sync option based on the encoder version.
|
||||
/// </summary>
|
||||
/// <param name="videoSync">The video sync parameter.</param>
|
||||
/// <param name="encoderVersion">The encoder version.</param>
|
||||
/// <returns>The video sync option string.</returns>
|
||||
public static string GetVideoSyncOption(string videoSync, Version encoderVersion)
|
||||
{
|
||||
if (string.IsNullOrEmpty(videoSync))
|
||||
@@ -7808,7 +7821,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
0 => " -fps_mode passthrough",
|
||||
1 => " -fps_mode cfr",
|
||||
2 => " -fps_mode vfr",
|
||||
_ => string.Empty
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "No+LvUzpMog=",
|
||||
"dgSpecHash": "oIB4cupu3LM=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\MediaBrowser.Controller\\MediaBrowser.Controller.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "MUG8U7vEPzE=",
|
||||
"dgSpecHash": "GeC+ga1Uo2A=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\MediaBrowser.LocalMetadata\\MediaBrowser.LocalMetadata.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "GTQOs1iNxFU=",
|
||||
"dgSpecHash": "aQOA3jWvZ/0=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\MediaBrowser.MediaEncoding\\MediaBrowser.MediaEncoding.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "O+ANw3v7loA=",
|
||||
"dgSpecHash": "ncCtXCalGV4=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\MediaBrowser.Providers\\MediaBrowser.Providers.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "rQazb3gGG1E=",
|
||||
"dgSpecHash": "P3fDKwImSWA=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\MediaBrowser.XbmcMetadata\\MediaBrowser.XbmcMetadata.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+1
-1
@@ -14,7 +14,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Jellyfin.CodeAnalysis")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+477045704e49d02668be2cddb91b218a9e70ec95")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+dbeec2e9f0c8f46c715f999e2be12d779e1fbd62")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Jellyfin.CodeAnalysis")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Jellyfin.CodeAnalysis")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0aa0b87087f971012539e7d2b22ae3fc0248cc13dafe15e7394876c75cd2b839
|
||||
9034528098f49c58c86624aecd413bc8526e219cc0c6d56bad4cbb6d5ce48570
|
||||
|
||||
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "7p0VjWNAkOM=",
|
||||
"dgSpecHash": "vHGJFlfns3Y=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\src\\Jellyfin.Database\\Jellyfin.Database.Providers.Sqlite\\Jellyfin.Database.Providers.Sqlite.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "HMW40QmobtY=",
|
||||
"dgSpecHash": "Fpoz11tqLl0=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\src\\Jellyfin.Drawing.Skia\\Jellyfin.Drawing.Skia.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -64,10 +64,10 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
IImageEncoder imageEncoder,
|
||||
IServerConfigurationManager config)
|
||||
{
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_imageEncoder = imageEncoder;
|
||||
_appPaths = appPaths;
|
||||
this._logger = logger;
|
||||
this._fileSystem = fileSystem;
|
||||
this._imageEncoder = imageEncoder;
|
||||
this._appPaths = appPaths;
|
||||
|
||||
var semaphoreCount = config.Configuration.ParallelImageEncodingLimit;
|
||||
if (semaphoreCount < 1)
|
||||
@@ -75,10 +75,10 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
semaphoreCount = Environment.ProcessorCount;
|
||||
}
|
||||
|
||||
_parallelEncodingLimit = new(semaphoreCount);
|
||||
this._parallelEncodingLimit = new(semaphoreCount);
|
||||
}
|
||||
|
||||
private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images");
|
||||
private string ResizedImageCachePath => Path.Combine(this._appPaths.ImageCachePath, "resized-images");
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyCollection<string> SupportedInputFormats =>
|
||||
@@ -113,11 +113,11 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SupportsImageCollageCreation => _imageEncoder.SupportsImageCollageCreation;
|
||||
public bool SupportsImageCollageCreation => this._imageEncoder.SupportsImageCollageCreation;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyCollection<ImageFormat> GetSupportedImageOutputFormats()
|
||||
=> _imageEncoder.SupportedOutputFormats;
|
||||
=> this._imageEncoder.SupportedOutputFormats;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options)
|
||||
@@ -134,12 +134,12 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
}
|
||||
|
||||
var mimeType = MimeTypes.GetMimeType(originalImagePath);
|
||||
if (!_imageEncoder.SupportsImageEncoding)
|
||||
if (!this._imageEncoder.SupportsImageEncoding)
|
||||
{
|
||||
return (originalImagePath, mimeType, dateModified);
|
||||
}
|
||||
|
||||
var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false);
|
||||
var supportedImageInfo = await this.GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false);
|
||||
originalImagePath = supportedImageInfo.Path;
|
||||
|
||||
// Original file doesn't exist, or original file is gif.
|
||||
@@ -179,8 +179,8 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
|
||||
int quality = options.Quality;
|
||||
|
||||
ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency);
|
||||
string cacheFilePath = GetCacheFilePath(
|
||||
ImageFormat outputFormat = this.GetOutputFormat(options.SupportedOutputFormats, requiresTransparency);
|
||||
string cacheFilePath = this.GetCacheFilePath(
|
||||
originalImagePath,
|
||||
options.Width,
|
||||
options.Height,
|
||||
@@ -204,9 +204,9 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
string resultPath;
|
||||
|
||||
// Limit number of parallel (more precisely: concurrent) image encodings to prevent a high memory usage
|
||||
using (await _parallelEncodingLimit.LockAsync().ConfigureAwait(false))
|
||||
using (await this._parallelEncodingLimit.LockAsync().ConfigureAwait(false))
|
||||
{
|
||||
resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat);
|
||||
resultPath = this._imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat);
|
||||
}
|
||||
|
||||
if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -215,19 +215,19 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
return (cacheFilePath, outputFormat.GetMimeType(), _fileSystem.GetLastWriteTimeUtc(cacheFilePath));
|
||||
return (cacheFilePath, outputFormat.GetMimeType(), this._fileSystem.GetLastWriteTimeUtc(cacheFilePath));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// If it fails for whatever reason, return the original image
|
||||
_logger.LogError(ex, "Error encoding image");
|
||||
this._logger.LogError(ex, "Error encoding image");
|
||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||
}
|
||||
}
|
||||
|
||||
private ImageFormat GetOutputFormat(IReadOnlyCollection<ImageFormat> clientSupportedFormats, bool requiresTransparency)
|
||||
{
|
||||
var serverFormats = GetSupportedImageOutputFormats();
|
||||
var serverFormats = this.GetSupportedImageOutputFormats();
|
||||
|
||||
// Client doesn't care about format, so start with webp if supported
|
||||
if (serverFormats.Contains(ImageFormat.Webp) && clientSupportedFormats.Contains(ImageFormat.Webp))
|
||||
@@ -354,7 +354,7 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
filename.Append(",v=");
|
||||
filename.Append(Version);
|
||||
|
||||
return GetCachePath(ResizedImageCachePath, filename.ToString(), format.GetExtension());
|
||||
return this.GetCachePath(this.ResizedImageCachePath, filename.ToString(), format.GetExtension());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -369,9 +369,9 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
}
|
||||
|
||||
string path = info.Path;
|
||||
_logger.LogDebug("Getting image size for item {ItemType} {Path}", item.GetType().Name, path);
|
||||
this._logger.LogDebug("Getting image size for item {ItemType} {Path}", item.GetType().Name, path);
|
||||
|
||||
ImageDimensions size = GetImageDimensions(path);
|
||||
ImageDimensions size = this.GetImageDimensions(path);
|
||||
info.Width = size.Width;
|
||||
info.Height = size.Height;
|
||||
|
||||
@@ -380,13 +380,13 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public ImageDimensions GetImageDimensions(string path)
|
||||
=> _imageEncoder.GetImageSize(path);
|
||||
=> this._imageEncoder.GetImageSize(path);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetImageBlurHash(string path)
|
||||
{
|
||||
var size = GetImageDimensions(path);
|
||||
return GetImageBlurHash(path, size);
|
||||
var size = this.GetImageDimensions(path);
|
||||
return this.GetImageBlurHash(path, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -406,7 +406,7 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
int xComp = Math.Min((int)xCompF + 1, 9);
|
||||
int yComp = Math.Min((int)yCompF + 1, 9);
|
||||
|
||||
return _imageEncoder.GetImageBlurHash(xComp, yComp, path);
|
||||
return this._imageEncoder.GetImageBlurHash(xComp, yComp, path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -415,11 +415,11 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
|
||||
=> GetImageCacheTag(item.Path, image.DateModified);
|
||||
=> this.GetImageCacheTag(item.Path, image.DateModified);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetImageCacheTag(BaseItemDto item, ItemImageInfo image)
|
||||
=> GetImageCacheTag(item.Path, image.DateModified);
|
||||
=> this.GetImageCacheTag(item.Path, image.DateModified);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? GetImageCacheTag(BaseItemDto item, ChapterInfo chapter)
|
||||
@@ -429,7 +429,7 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetImageCacheTag(item.Path, chapter.ImageDateModified);
|
||||
return this.GetImageCacheTag(item.Path, chapter.ImageDateModified);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -440,7 +440,7 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetImageCacheTag(item, new ItemImageInfo
|
||||
return this.GetImageCacheTag(item, new ItemImageInfo
|
||||
{
|
||||
Path = chapter.ImagePath,
|
||||
Type = ImageType.Chapter,
|
||||
@@ -456,7 +456,7 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetImageCacheTag(user.ProfileImage.Path, user.ProfileImage.LastModified);
|
||||
return this.GetImageCacheTag(user.ProfileImage.Path, user.ProfileImage.LastModified);
|
||||
}
|
||||
|
||||
private Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
|
||||
@@ -494,7 +494,7 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
|
||||
var filename = uniqueName.GetMD5() + fileExtension;
|
||||
|
||||
return GetCachePath(path, filename);
|
||||
return this.GetCachePath(path, filename);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -528,28 +528,28 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
/// <inheritdoc />
|
||||
public void CreateImageCollage(ImageCollageOptions options, string? libraryName)
|
||||
{
|
||||
_logger.LogDebug("Creating image collage and saving to {Path}", options.OutputPath);
|
||||
this._logger.LogDebug("Creating image collage and saving to {Path}", options.OutputPath);
|
||||
|
||||
_imageEncoder.CreateImageCollage(options, libraryName);
|
||||
this._imageEncoder.CreateImageCollage(options, libraryName);
|
||||
|
||||
_logger.LogDebug("Completed creation of image collage and saved to {Path}", options.OutputPath);
|
||||
this._logger.LogDebug("Completed creation of image collage and saved to {Path}", options.OutputPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
if (this._disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_imageEncoder is IDisposable disposable)
|
||||
if (this._imageEncoder is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
_parallelEncodingLimit?.Dispose();
|
||||
this._parallelEncodingLimit?.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
this._disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "kfdiYX/qdMo=",
|
||||
"dgSpecHash": "Vcdm+k15Tts=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\src\\Jellyfin.Drawing\\Jellyfin.Drawing.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "Wq1Uahcka68=",
|
||||
"dgSpecHash": "9I/zz4fLYTY=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\src\\Jellyfin.LiveTv\\Jellyfin.LiveTv.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "2kUNnZdwGG8=",
|
||||
"dgSpecHash": "svddOISkg9E=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\src\\Jellyfin.MediaEncoding.Hls\\Jellyfin.MediaEncoding.Hls.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -76,60 +76,62 @@ public static class FfProbeKeyframeExtractor
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the keyframe data from the FFProbe stream.
|
||||
/// </summary>
|
||||
/// <param name="reader">The stream reader containing FFProbe output.</param>
|
||||
/// <returns>The parsed keyframe data.</returns>
|
||||
internal static KeyframeData ParseStream(StreamReader reader)
|
||||
{
|
||||
var keyframes = new List<long>();
|
||||
double streamDuration = 0;
|
||||
double formatDuration = 0;
|
||||
|
||||
using (reader)
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
while (!reader.EndOfStream)
|
||||
var line = reader.ReadLine().AsSpan();
|
||||
if (line.IsEmpty)
|
||||
{
|
||||
var line = reader.ReadLine().AsSpan();
|
||||
if (line.IsEmpty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var firstComma = line.IndexOf(',');
|
||||
var lineType = line[..firstComma];
|
||||
var rest = line[(firstComma + 1)..];
|
||||
if (lineType.Equals("packet", StringComparison.OrdinalIgnoreCase))
|
||||
var firstComma = line.IndexOf(',');
|
||||
var lineType = line[..firstComma];
|
||||
var rest = line[(firstComma + 1)..];
|
||||
if (lineType.Equals("packet", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Split time and flags from the packet line. Example line: packet,7169.079000,K_
|
||||
var secondComma = rest.IndexOf(',');
|
||||
var ptsTime = rest[..secondComma];
|
||||
var flags = rest[(secondComma + 1)..];
|
||||
if (flags.StartsWith("K_"))
|
||||
{
|
||||
// Split time and flags from the packet line. Example line: packet,7169.079000,K_
|
||||
var secondComma = rest.IndexOf(',');
|
||||
var ptsTime = rest[..secondComma];
|
||||
var flags = rest[(secondComma + 1)..];
|
||||
if (flags.StartsWith("K_"))
|
||||
if (double.TryParse(ptsTime, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var keyframe))
|
||||
{
|
||||
if (double.TryParse(ptsTime, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var keyframe))
|
||||
{
|
||||
// Have to manually convert to ticks to avoid rounding errors as TimeSpan is only precise down to 1 ms when converting double.
|
||||
keyframes.Add(Convert.ToInt64(keyframe * TimeSpan.TicksPerSecond));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (lineType.Equals("stream", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var streamDurationResult))
|
||||
{
|
||||
streamDuration = streamDurationResult;
|
||||
}
|
||||
}
|
||||
else if (lineType.Equals("format", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var formatDurationResult))
|
||||
{
|
||||
formatDuration = formatDurationResult;
|
||||
// Have to manually convert to ticks to avoid rounding errors as TimeSpan is only precise down to 1 ms when converting double.
|
||||
keyframes.Add(Convert.ToInt64(keyframe * TimeSpan.TicksPerSecond));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer the stream duration as it should be more accurate
|
||||
var duration = streamDuration > 0 ? streamDuration : formatDuration;
|
||||
|
||||
return new KeyframeData(TimeSpan.FromSeconds(duration).Ticks, keyframes);
|
||||
else if (lineType.Equals("stream", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var streamDurationResult))
|
||||
{
|
||||
streamDuration = streamDurationResult;
|
||||
}
|
||||
}
|
||||
else if (lineType.Equals("format", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (double.TryParse(rest, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var formatDurationResult))
|
||||
{
|
||||
formatDuration = formatDurationResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer the stream duration as it should be more accurate
|
||||
var duration = streamDuration > 0 ? streamDuration : formatDuration;
|
||||
|
||||
return new KeyframeData(TimeSpan.FromSeconds(duration).Ticks, keyframes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ public class KeyframeData
|
||||
/// <param name="keyframeTicks">The video keyframes in ticks.</param>
|
||||
public KeyframeData(long totalDuration, IReadOnlyList<long> keyframeTicks)
|
||||
{
|
||||
TotalDuration = totalDuration;
|
||||
KeyframeTicks = keyframeTicks;
|
||||
this.TotalDuration = totalDuration;
|
||||
this.KeyframeTicks = keyframeTicks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -70,6 +70,7 @@ internal static class EbmlReaderExtensions
|
||||
long? tracksPosition = null;
|
||||
long? cuesPosition = null;
|
||||
long? infoPosition = null;
|
||||
|
||||
// The first element should be a SeekHead otherwise we'll have to search manually
|
||||
if (!reader.FindElement(MatroskaConstants.SeekHead))
|
||||
{
|
||||
@@ -128,6 +129,7 @@ internal static class EbmlReaderExtensions
|
||||
|
||||
double? duration = null;
|
||||
reader.EnterContainer();
|
||||
|
||||
// Mandatory element
|
||||
reader.FindElement(MatroskaConstants.TimestampScale);
|
||||
var timestampScale = reader.ReadUInt();
|
||||
@@ -158,6 +160,7 @@ internal static class EbmlReaderExtensions
|
||||
while (reader.FindElement(MatroskaConstants.TrackEntry))
|
||||
{
|
||||
reader.EnterContainer();
|
||||
|
||||
// Mandatory element
|
||||
reader.FindElement(MatroskaConstants.TrackNumber);
|
||||
var trackNumber = reader.ReadUInt();
|
||||
|
||||
@@ -9,26 +9,88 @@ namespace Jellyfin.MediaEncoding.Keyframes.Matroska;
|
||||
/// </summary>
|
||||
public static class MatroskaConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// The Matroska segment container identifier.
|
||||
/// </summary>
|
||||
internal const ulong SegmentContainer = 0x18538067;
|
||||
|
||||
/// <summary>
|
||||
/// The Matroska seek head identifier.
|
||||
/// </summary>
|
||||
internal const ulong SeekHead = 0x114D9B74;
|
||||
|
||||
/// <summary>
|
||||
/// The Matroska seek identifier.
|
||||
/// </summary>
|
||||
internal const ulong Seek = 0x4DBB;
|
||||
|
||||
/// <summary>
|
||||
/// The Matroska info identifier.
|
||||
/// </summary>
|
||||
internal const ulong Info = 0x1549A966;
|
||||
|
||||
/// <summary>
|
||||
/// The Matroska timestamp scale identifier.
|
||||
/// </summary>
|
||||
internal const ulong TimestampScale = 0x2AD7B1;
|
||||
|
||||
/// <summary>
|
||||
/// The Matroska duration identifier.
|
||||
/// </summary>
|
||||
internal const ulong Duration = 0x4489;
|
||||
|
||||
/// <summary>
|
||||
/// The Matroska tracks identifier.
|
||||
/// </summary>
|
||||
internal const ulong Tracks = 0x1654AE6B;
|
||||
|
||||
/// <summary>
|
||||
/// The Matroska track entry identifier.
|
||||
/// </summary>
|
||||
internal const ulong TrackEntry = 0xAE;
|
||||
|
||||
/// <summary>
|
||||
/// The Matroska track number identifier.
|
||||
/// </summary>
|
||||
internal const ulong TrackNumber = 0xD7;
|
||||
|
||||
/// <summary>
|
||||
/// The Matroska track type identifier.
|
||||
/// </summary>
|
||||
internal const ulong TrackType = 0x83;
|
||||
|
||||
/// <summary>
|
||||
/// The Matroska video track type value.
|
||||
/// </summary>
|
||||
internal const ulong TrackTypeVideo = 0x1;
|
||||
|
||||
/// <summary>
|
||||
/// The Matroska subtitle track type value.
|
||||
/// </summary>
|
||||
internal const ulong TrackTypeSubtitle = 0x11;
|
||||
|
||||
/// <summary>
|
||||
/// The Matroska cues identifier.
|
||||
/// </summary>
|
||||
internal const ulong Cues = 0x1C53BB6B;
|
||||
|
||||
/// <summary>
|
||||
/// The Matroska cue time identifier.
|
||||
/// </summary>
|
||||
internal const ulong CueTime = 0xB3;
|
||||
|
||||
/// <summary>
|
||||
/// The Matroska cue point identifier.
|
||||
/// </summary>
|
||||
internal const ulong CuePoint = 0xBB;
|
||||
|
||||
/// <summary>
|
||||
/// The Matroska cue track positions identifier.
|
||||
/// </summary>
|
||||
internal const ulong CueTrackPositions = 0xB7;
|
||||
|
||||
/// <summary>
|
||||
/// The Matroska cue point track number identifier.
|
||||
/// </summary>
|
||||
internal const ulong CuePointTrackNumber = 0xF7;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ public static class MatroskaKeyframeExtractor
|
||||
using var reader = new EbmlReader(stream);
|
||||
|
||||
var seekHead = reader.ReadSeekHead();
|
||||
|
||||
// External lib does not support seeking backwards (yet)
|
||||
Info info;
|
||||
ulong videoTrackNumber;
|
||||
@@ -49,6 +50,7 @@ public static class MatroskaKeyframeExtractor
|
||||
{
|
||||
reader.EnterContainer();
|
||||
ulong? trackNumber = null;
|
||||
|
||||
// Mandatory element
|
||||
reader.FindElement(MatroskaConstants.CueTime);
|
||||
var cueTime = reader.ReadUInt();
|
||||
|
||||
@@ -16,8 +16,8 @@ internal class Info
|
||||
/// <param name="duration">The duration of the entire file.</param>
|
||||
public Info(long timestampScale, double? duration)
|
||||
{
|
||||
TimestampScale = timestampScale;
|
||||
Duration = duration;
|
||||
this.TimestampScale = timestampScale;
|
||||
this.Duration = duration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -17,9 +17,9 @@ internal class SeekHead
|
||||
/// <param name="cuesPosition">The relative file position of the cues segment.</param>
|
||||
public SeekHead(long infoPosition, long tracksPosition, long cuesPosition)
|
||||
{
|
||||
InfoPosition = infoPosition;
|
||||
TracksPosition = tracksPosition;
|
||||
CuesPosition = cuesPosition;
|
||||
this.InfoPosition = infoPosition;
|
||||
this.TracksPosition = tracksPosition;
|
||||
this.CuesPosition = cuesPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "mTVFQQSIGo8=",
|
||||
"dgSpecHash": "fWFpFkhudto=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\src\\Jellyfin.MediaEncoding.Keyframes\\Jellyfin.MediaEncoding.Keyframes.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "o8ef9/49oOU=",
|
||||
"dgSpecHash": "nYThmTXT+N4=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\src\\Jellyfin.Networking\\Jellyfin.Networking.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "I6cvgut0KE0=",
|
||||
"dgSpecHash": "jz3od75Vj9o=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\tests\\Jellyfin.Api.Tests\\Jellyfin.Api.Tests.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "rSTC0c4hKeg=",
|
||||
"dgSpecHash": "D3+az8yIj/g=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\tests\\Jellyfin.Common.Tests\\Jellyfin.Common.Tests.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -13,8 +13,17 @@ namespace Jellyfin.Controller.Tests
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="BaseItemManager"/>.
|
||||
/// </summary>
|
||||
public class BaseItemManagerTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests that IsMetadataFetcherEnabled checks library and server configuration options.
|
||||
/// </summary>
|
||||
/// <param name="itemType">The type of item to test.</param>
|
||||
/// <param name="fetcherName">The name of the metadata fetcher.</param>
|
||||
/// <param name="expected">The expected result.</param>
|
||||
[Theory]
|
||||
[InlineData(typeof(Book), "LibraryEnabled", true)]
|
||||
[InlineData(typeof(Book), "LibraryDisabled", false)]
|
||||
@@ -24,30 +33,36 @@ namespace Jellyfin.Controller.Tests
|
||||
{
|
||||
BaseItem item = (BaseItem)Activator.CreateInstance(itemType)!;
|
||||
|
||||
var libraryTypeOptions = itemType == typeof(Book)
|
||||
TypeOptions? libraryTypeOptions = itemType == typeof(Book)
|
||||
? new TypeOptions
|
||||
{
|
||||
Type = "Book",
|
||||
MetadataFetchers = new[] { "LibraryEnabled" }
|
||||
MetadataFetchers = ["LibraryEnabled"],
|
||||
}
|
||||
: null;
|
||||
|
||||
var serverConfiguration = new ServerConfiguration();
|
||||
foreach (var typeConfig in serverConfiguration.MetadataOptions)
|
||||
ServerConfiguration serverConfiguration = new();
|
||||
foreach (MetadataOptions typeConfig in serverConfiguration.MetadataOptions)
|
||||
{
|
||||
typeConfig.DisabledMetadataFetchers = new[] { "ServerDisabled" };
|
||||
typeConfig.DisabledMetadataFetchers = ["ServerDisabled"];
|
||||
}
|
||||
|
||||
var serverConfigurationManager = new Mock<IServerConfigurationManager>();
|
||||
serverConfigurationManager.Setup(scm => scm.Configuration)
|
||||
Mock<IServerConfigurationManager> serverConfigurationManager = new();
|
||||
_ = serverConfigurationManager.Setup(scm => scm.Configuration)
|
||||
.Returns(serverConfiguration);
|
||||
|
||||
var baseItemManager = new BaseItemManager(serverConfigurationManager.Object);
|
||||
var actual = baseItemManager.IsMetadataFetcherEnabled(item, libraryTypeOptions, fetcherName);
|
||||
BaseItemManager baseItemManager = new(serverConfigurationManager.Object);
|
||||
bool actual = baseItemManager.IsMetadataFetcherEnabled(item, libraryTypeOptions, fetcherName);
|
||||
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that IsImageFetcherEnabled checks library and server configuration options.
|
||||
/// </summary>
|
||||
/// <param name="itemType">The type of item to test.</param>
|
||||
/// <param name="fetcherName">The name of the image fetcher.</param>
|
||||
/// <param name="expected">The expected result.</param>
|
||||
[Theory]
|
||||
[InlineData(typeof(Book), "LibraryEnabled", true)]
|
||||
[InlineData(typeof(Book), "LibraryDisabled", false)]
|
||||
@@ -57,26 +72,26 @@ namespace Jellyfin.Controller.Tests
|
||||
{
|
||||
BaseItem item = (BaseItem)Activator.CreateInstance(itemType)!;
|
||||
|
||||
var libraryTypeOptions = itemType == typeof(Book)
|
||||
TypeOptions? libraryTypeOptions = itemType == typeof(Book)
|
||||
? new TypeOptions
|
||||
{
|
||||
Type = "Book",
|
||||
ImageFetchers = new[] { "LibraryEnabled" }
|
||||
ImageFetchers = ["LibraryEnabled"],
|
||||
}
|
||||
: null;
|
||||
|
||||
var serverConfiguration = new ServerConfiguration();
|
||||
foreach (var typeConfig in serverConfiguration.MetadataOptions)
|
||||
ServerConfiguration serverConfiguration = new();
|
||||
foreach (MetadataOptions typeConfig in serverConfiguration.MetadataOptions)
|
||||
{
|
||||
typeConfig.DisabledImageFetchers = new[] { "ServerDisabled" };
|
||||
typeConfig.DisabledImageFetchers = ["ServerDisabled"];
|
||||
}
|
||||
|
||||
var serverConfigurationManager = new Mock<IServerConfigurationManager>();
|
||||
serverConfigurationManager.Setup(scm => scm.Configuration)
|
||||
Mock<IServerConfigurationManager> serverConfigurationManager = new();
|
||||
_ = serverConfigurationManager.Setup(scm => scm.Configuration)
|
||||
.Returns(serverConfiguration);
|
||||
|
||||
var baseItemManager = new BaseItemManager(serverConfigurationManager.Object);
|
||||
var actual = baseItemManager.IsImageFetcherEnabled(item, libraryTypeOptions, fetcherName);
|
||||
BaseItemManager baseItemManager = new(serverConfigurationManager.Object);
|
||||
bool actual = baseItemManager.IsImageFetcherEnabled(item, libraryTypeOptions, fetcherName);
|
||||
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
@@ -4,124 +4,140 @@
|
||||
|
||||
namespace Jellyfin.Controller.Tests
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="DirectoryService"/>.
|
||||
/// </summary>
|
||||
public class DirectoryServiceTests
|
||||
{
|
||||
private const string LowerCasePath = "/music/someartist";
|
||||
private const string UpperCasePath = "/music/SOMEARTIST";
|
||||
|
||||
private static readonly FileSystemMetadata[] _lowerCaseFileSystemMetadata =
|
||||
{
|
||||
private static readonly FileSystemMetadata[] LowerCaseFileSystemMetadata =
|
||||
[
|
||||
new()
|
||||
{
|
||||
FullName = LowerCasePath + "/Artwork",
|
||||
IsDirectory = true
|
||||
IsDirectory = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FullName = LowerCasePath + "/Some Other Folder",
|
||||
IsDirectory = true
|
||||
IsDirectory = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FullName = LowerCasePath + "/Song 2.mp3",
|
||||
IsDirectory = false
|
||||
IsDirectory = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FullName = LowerCasePath + "/Song 3.mp3",
|
||||
IsDirectory = false
|
||||
}
|
||||
};
|
||||
IsDirectory = false,
|
||||
},
|
||||
];
|
||||
|
||||
private static readonly FileSystemMetadata[] _upperCaseFileSystemMetadata =
|
||||
{
|
||||
private static readonly FileSystemMetadata[] UpperCaseFileSystemMetadata =
|
||||
[
|
||||
new()
|
||||
{
|
||||
FullName = UpperCasePath + "/Lyrics",
|
||||
IsDirectory = true
|
||||
IsDirectory = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FullName = UpperCasePath + "/Song 1.mp3",
|
||||
IsDirectory = false
|
||||
}
|
||||
};
|
||||
IsDirectory = false,
|
||||
},
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Tests that GetFileSystemEntries caches entries for paths with different casing.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetFileSystemEntries_GivenPathsWithDifferentCasing_CachesAll()
|
||||
{
|
||||
var fileSystemMock = new Mock<IFileSystem>();
|
||||
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(_upperCaseFileSystemMetadata);
|
||||
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(_lowerCaseFileSystemMetadata);
|
||||
var directoryService = new DirectoryService(fileSystemMock.Object);
|
||||
Mock<IFileSystem> fileSystemMock = new();
|
||||
_ = fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(UpperCaseFileSystemMetadata);
|
||||
_ = fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(LowerCaseFileSystemMetadata);
|
||||
DirectoryService directoryService = new(fileSystemMock.Object);
|
||||
|
||||
var upperCaseResult = directoryService.GetFileSystemEntries(UpperCasePath);
|
||||
var lowerCaseResult = directoryService.GetFileSystemEntries(LowerCasePath);
|
||||
FileSystemMetadata[] upperCaseResult = directoryService.GetFileSystemEntries(UpperCasePath);
|
||||
FileSystemMetadata[] lowerCaseResult = directoryService.GetFileSystemEntries(LowerCasePath);
|
||||
|
||||
Assert.Equal(_upperCaseFileSystemMetadata, upperCaseResult);
|
||||
Assert.Equal(_lowerCaseFileSystemMetadata, lowerCaseResult);
|
||||
Assert.Equal(UpperCaseFileSystemMetadata, upperCaseResult);
|
||||
Assert.Equal(LowerCaseFileSystemMetadata, lowerCaseResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that GetFiles returns correct files for paths with different casing.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetFiles_GivenPathsWithDifferentCasing_ReturnsCorrectFiles()
|
||||
{
|
||||
var fileSystemMock = new Mock<IFileSystem>();
|
||||
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(_upperCaseFileSystemMetadata);
|
||||
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(_lowerCaseFileSystemMetadata);
|
||||
var directoryService = new DirectoryService(fileSystemMock.Object);
|
||||
Mock<IFileSystem> fileSystemMock = new();
|
||||
_ = fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(UpperCaseFileSystemMetadata);
|
||||
_ = fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(LowerCaseFileSystemMetadata);
|
||||
DirectoryService directoryService = new(fileSystemMock.Object);
|
||||
|
||||
var upperCaseResult = directoryService.GetFiles(UpperCasePath);
|
||||
var lowerCaseResult = directoryService.GetFiles(LowerCasePath);
|
||||
List<FileSystemMetadata> upperCaseResult = directoryService.GetFiles(UpperCasePath);
|
||||
List<FileSystemMetadata> lowerCaseResult = directoryService.GetFiles(LowerCasePath);
|
||||
|
||||
Assert.Equal(_upperCaseFileSystemMetadata.Where(f => !f.IsDirectory), upperCaseResult);
|
||||
Assert.Equal(_lowerCaseFileSystemMetadata.Where(f => !f.IsDirectory), lowerCaseResult);
|
||||
Assert.Equal(UpperCaseFileSystemMetadata.Where(f => !f.IsDirectory), upperCaseResult);
|
||||
Assert.Equal(LowerCaseFileSystemMetadata.Where(f => !f.IsDirectory), lowerCaseResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that GetDirectories returns correct directories for paths with different casing.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetDirectories_GivenPathsWithDifferentCasing_ReturnsCorrectDirectories()
|
||||
{
|
||||
var fileSystemMock = new Mock<IFileSystem>();
|
||||
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(_upperCaseFileSystemMetadata);
|
||||
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(_lowerCaseFileSystemMetadata);
|
||||
var directoryService = new DirectoryService(fileSystemMock.Object);
|
||||
Mock<IFileSystem> fileSystemMock = new();
|
||||
_ = fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(UpperCaseFileSystemMetadata);
|
||||
_ = fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(LowerCaseFileSystemMetadata);
|
||||
DirectoryService directoryService = new(fileSystemMock.Object);
|
||||
|
||||
var upperCaseResult = directoryService.GetDirectories(UpperCasePath);
|
||||
var lowerCaseResult = directoryService.GetDirectories(LowerCasePath);
|
||||
List<FileSystemMetadata> upperCaseResult = directoryService.GetDirectories(UpperCasePath);
|
||||
List<FileSystemMetadata> lowerCaseResult = directoryService.GetDirectories(LowerCasePath);
|
||||
|
||||
Assert.Equal(_upperCaseFileSystemMetadata.Where(f => f.IsDirectory), upperCaseResult);
|
||||
Assert.Equal(_lowerCaseFileSystemMetadata.Where(f => f.IsDirectory), lowerCaseResult);
|
||||
Assert.Equal(UpperCaseFileSystemMetadata.Where(f => f.IsDirectory), upperCaseResult);
|
||||
Assert.Equal(LowerCaseFileSystemMetadata.Where(f => f.IsDirectory), lowerCaseResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that GetFile returns correct file for paths with different casing.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetFile_GivenFilePathsWithDifferentCasing_ReturnsCorrectFile()
|
||||
{
|
||||
const string lowerCasePath = "/music/someartist/song 1.mp3";
|
||||
var lowerCaseFileSystemMetadata = new FileSystemMetadata
|
||||
FileSystemMetadata lowerCaseFileSystemMetadata = new()
|
||||
{
|
||||
FullName = lowerCasePath,
|
||||
Exists = true
|
||||
Exists = true,
|
||||
};
|
||||
const string upperCasePath = "/music/SOMEARTIST/SONG 1.mp3";
|
||||
var upperCaseFileSystemMetadata = new FileSystemMetadata
|
||||
FileSystemMetadata upperCaseFileSystemMetadata = new()
|
||||
{
|
||||
FullName = upperCasePath,
|
||||
Exists = false
|
||||
Exists = false,
|
||||
};
|
||||
var fileSystemMock = new Mock<IFileSystem>();
|
||||
fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == upperCasePath))).Returns(upperCaseFileSystemMetadata);
|
||||
fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == lowerCasePath))).Returns(lowerCaseFileSystemMetadata);
|
||||
var directoryService = new DirectoryService(fileSystemMock.Object);
|
||||
Mock<IFileSystem> fileSystemMock = new();
|
||||
_ = fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == upperCasePath))).Returns(upperCaseFileSystemMetadata);
|
||||
_ = fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == lowerCasePath))).Returns(lowerCaseFileSystemMetadata);
|
||||
DirectoryService directoryService = new(fileSystemMock.Object);
|
||||
|
||||
var lowerCaseDirResult = directoryService.GetDirectory(lowerCasePath);
|
||||
var lowerCaseFileResult = directoryService.GetFile(lowerCasePath);
|
||||
var upperCaseDirResult = directoryService.GetDirectory(upperCasePath);
|
||||
var upperCaseFileResult = directoryService.GetFile(upperCasePath);
|
||||
FileSystemMetadata? lowerCaseDirResult = directoryService.GetDirectory(lowerCasePath);
|
||||
FileSystemMetadata? lowerCaseFileResult = directoryService.GetFile(lowerCasePath);
|
||||
FileSystemMetadata? upperCaseDirResult = directoryService.GetDirectory(upperCasePath);
|
||||
FileSystemMetadata? upperCaseFileResult = directoryService.GetFile(upperCasePath);
|
||||
|
||||
Assert.Null(lowerCaseDirResult);
|
||||
Assert.Equal(lowerCaseFileSystemMetadata, lowerCaseFileResult);
|
||||
@@ -129,32 +145,35 @@ namespace Jellyfin.Controller.Tests
|
||||
Assert.Null(upperCaseFileResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that GetDirectory returns correct directory for paths with different casing.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetDirectory_GivenFilePathsWithDifferentCasing_ReturnsCorrectDirectory()
|
||||
{
|
||||
const string lowerCasePath = "/music/someartist/Lyrics";
|
||||
var lowerCaseFileSystemMetadata = new FileSystemMetadata
|
||||
FileSystemMetadata lowerCaseFileSystemMetadata = new()
|
||||
{
|
||||
FullName = lowerCasePath,
|
||||
IsDirectory = true,
|
||||
Exists = true
|
||||
Exists = true,
|
||||
};
|
||||
const string upperCasePath = "/music/SOMEARTIST/LYRICS";
|
||||
var upperCaseFileSystemMetadata = new FileSystemMetadata
|
||||
FileSystemMetadata upperCaseFileSystemMetadata = new()
|
||||
{
|
||||
FullName = upperCasePath,
|
||||
IsDirectory = true,
|
||||
Exists = false
|
||||
Exists = false,
|
||||
};
|
||||
var fileSystemMock = new Mock<IFileSystem>();
|
||||
fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == upperCasePath))).Returns(upperCaseFileSystemMetadata);
|
||||
fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == lowerCasePath))).Returns(lowerCaseFileSystemMetadata);
|
||||
var directoryService = new DirectoryService(fileSystemMock.Object);
|
||||
Mock<IFileSystem> fileSystemMock = new();
|
||||
_ = fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == upperCasePath))).Returns(upperCaseFileSystemMetadata);
|
||||
_ = fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == lowerCasePath))).Returns(lowerCaseFileSystemMetadata);
|
||||
DirectoryService directoryService = new(fileSystemMock.Object);
|
||||
|
||||
var lowerCaseDirResult = directoryService.GetDirectory(lowerCasePath);
|
||||
var lowerCaseFileResult = directoryService.GetFile(lowerCasePath);
|
||||
var upperCaseDirResult = directoryService.GetDirectory(upperCasePath);
|
||||
var upperCaseFileResult = directoryService.GetFile(upperCasePath);
|
||||
FileSystemMetadata? lowerCaseDirResult = directoryService.GetDirectory(lowerCasePath);
|
||||
FileSystemMetadata? lowerCaseFileResult = directoryService.GetFile(lowerCasePath);
|
||||
FileSystemMetadata? upperCaseDirResult = directoryService.GetDirectory(upperCasePath);
|
||||
FileSystemMetadata? upperCaseFileResult = directoryService.GetFile(upperCasePath);
|
||||
|
||||
Assert.Equal(lowerCaseFileSystemMetadata, lowerCaseDirResult);
|
||||
Assert.Null(lowerCaseFileResult);
|
||||
@@ -162,92 +181,101 @@ namespace Jellyfin.Controller.Tests
|
||||
Assert.Null(upperCaseFileResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that GetFile returns cached file when the path is already cached.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetFile_GivenCachedPath_ReturnsCachedFile()
|
||||
{
|
||||
const string path = "/music/someartist/song 1.mp3";
|
||||
var cachedFileSystemMetadata = new FileSystemMetadata
|
||||
FileSystemMetadata cachedFileSystemMetadata = new()
|
||||
{
|
||||
FullName = path,
|
||||
Exists = true
|
||||
Exists = true,
|
||||
};
|
||||
var newFileSystemMetadata = new FileSystemMetadata
|
||||
FileSystemMetadata newFileSystemMetadata = new()
|
||||
{
|
||||
FullName = "/music/SOMEARTIST/song 1.mp3",
|
||||
Exists = true
|
||||
Exists = true,
|
||||
};
|
||||
|
||||
var fileSystemMock = new Mock<IFileSystem>();
|
||||
fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == path))).Returns(cachedFileSystemMetadata);
|
||||
var directoryService = new DirectoryService(fileSystemMock.Object);
|
||||
Mock<IFileSystem> fileSystemMock = new();
|
||||
_ = fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == path))).Returns(cachedFileSystemMetadata);
|
||||
DirectoryService directoryService = new(fileSystemMock.Object);
|
||||
|
||||
var result = directoryService.GetFile(path);
|
||||
fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == path))).Returns(newFileSystemMetadata);
|
||||
var secondResult = directoryService.GetFile(path);
|
||||
FileSystemMetadata? result = directoryService.GetFile(path);
|
||||
_ = fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == path))).Returns(newFileSystemMetadata);
|
||||
FileSystemMetadata? secondResult = directoryService.GetFile(path);
|
||||
|
||||
Assert.Equivalent(cachedFileSystemMetadata, result);
|
||||
Assert.Equivalent(cachedFileSystemMetadata, secondResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that GetFilePaths returns only cached paths when clear is not called.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetFilePaths_GivenCachedFilePathWithoutClear_ReturnsOnlyCachedPaths()
|
||||
{
|
||||
const string path = "/music/someartist";
|
||||
|
||||
var cachedPaths = new[]
|
||||
{
|
||||
string[] cachedPaths =
|
||||
[
|
||||
"/music/someartist/song 1.mp3",
|
||||
"/music/someartist/song 2.mp3",
|
||||
"/music/someartist/song 3.mp3",
|
||||
"/music/someartist/song 4.mp3",
|
||||
};
|
||||
var newPaths = new[]
|
||||
{
|
||||
];
|
||||
string[] newPaths =
|
||||
[
|
||||
"/music/someartist/song 5.mp3",
|
||||
"/music/someartist/song 6.mp3",
|
||||
"/music/someartist/song 7.mp3",
|
||||
"/music/someartist/song 8.mp3",
|
||||
};
|
||||
];
|
||||
|
||||
var fileSystemMock = new Mock<IFileSystem>();
|
||||
fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths);
|
||||
var directoryService = new DirectoryService(fileSystemMock.Object);
|
||||
Mock<IFileSystem> fileSystemMock = new();
|
||||
_ = fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths);
|
||||
DirectoryService directoryService = new(fileSystemMock.Object);
|
||||
|
||||
var result = directoryService.GetFilePaths(path);
|
||||
fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths);
|
||||
var secondResult = directoryService.GetFilePaths(path);
|
||||
IReadOnlyList<string> result = directoryService.GetFilePaths(path);
|
||||
_ = fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths);
|
||||
IReadOnlyList<string> secondResult = directoryService.GetFilePaths(path);
|
||||
|
||||
Assert.Equal(cachedPaths, result);
|
||||
Assert.Equal(cachedPaths, secondResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that GetFilePaths returns new paths when clear is called.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetFilePaths_GivenCachedFilePathWithClear_ReturnsNewPaths()
|
||||
{
|
||||
const string path = "/music/someartist";
|
||||
|
||||
var cachedPaths = new[]
|
||||
{
|
||||
string[] cachedPaths =
|
||||
[
|
||||
"/music/someartist/song 1.mp3",
|
||||
"/music/someartist/song 2.mp3",
|
||||
"/music/someartist/song 3.mp3",
|
||||
"/music/someartist/song 4.mp3",
|
||||
};
|
||||
var newPaths = new[]
|
||||
{
|
||||
];
|
||||
string[] newPaths =
|
||||
[
|
||||
"/music/someartist/song 5.mp3",
|
||||
"/music/someartist/song 6.mp3",
|
||||
"/music/someartist/song 7.mp3",
|
||||
"/music/someartist/song 8.mp3",
|
||||
};
|
||||
];
|
||||
|
||||
var fileSystemMock = new Mock<IFileSystem>();
|
||||
fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths);
|
||||
var directoryService = new DirectoryService(fileSystemMock.Object);
|
||||
Mock<IFileSystem> fileSystemMock = new();
|
||||
_ = fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths);
|
||||
DirectoryService directoryService = new(fileSystemMock.Object);
|
||||
|
||||
var result = directoryService.GetFilePaths(path);
|
||||
fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths);
|
||||
var secondResult = directoryService.GetFilePaths(path, true);
|
||||
IReadOnlyList<string> result = directoryService.GetFilePaths(path);
|
||||
_ = fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths);
|
||||
IReadOnlyList<string> secondResult = directoryService.GetFilePaths(path, true);
|
||||
|
||||
Assert.Equal(cachedPaths, result);
|
||||
Assert.Equal(newPaths, secondResult);
|
||||
|
||||
@@ -2,49 +2,67 @@
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace Jellyfin.Controller.Tests.Entities;
|
||||
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
public class BaseItemTests
|
||||
namespace Jellyfin.Controller.Tests.Entities
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("", "")]
|
||||
[InlineData("1", "0000000001")]
|
||||
[InlineData("t", "t")]
|
||||
[InlineData("test", "test")]
|
||||
[InlineData("test1", "test0000000001")]
|
||||
[InlineData("1test 2", "0000000001test 0000000002")]
|
||||
public void BaseItem_ModifySortChunks_Valid(string input, string expected)
|
||||
=> Assert.Equal(expected, BaseItem.ModifySortChunks(input));
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Movies/Ted/Ted.mp4", "/Movies/Ted/Ted - Unrated Edition.mp4", "Ted", "Unrated Edition")]
|
||||
[InlineData("/Movies/Deadpool 2 (2018)/Deadpool 2 (2018).mkv", "/Movies/Deadpool 2 (2018)/Deadpool 2 (2018) - Super Duper Cut.mkv", "Deadpool 2 (2018)", "Super Duper Cut")]
|
||||
public void GetMediaSourceName_Valid(string primaryPath, string altPath, string name, string altName)
|
||||
/// <summary>
|
||||
/// Tests for <see cref="BaseItem"/>.
|
||||
/// </summary>
|
||||
public class BaseItemTests
|
||||
{
|
||||
var mediaSourceManager = new Mock<IMediaSourceManager>();
|
||||
mediaSourceManager.Setup(x => x.GetPathProtocol(It.IsAny<string>()))
|
||||
.Returns((string x) => MediaProtocol.File);
|
||||
BaseItem.MediaSourceManager = mediaSourceManager.Object;
|
||||
|
||||
var video = new Video()
|
||||
/// <summary>
|
||||
/// Tests that ModifySortChunks correctly modifies sort chunks.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string.</param>
|
||||
/// <param name="expected">The expected output string.</param>
|
||||
[Theory]
|
||||
[InlineData("", "")]
|
||||
[InlineData("1", "0000000001")]
|
||||
[InlineData("t", "t")]
|
||||
[InlineData("test", "test")]
|
||||
[InlineData("test1", "test0000000001")]
|
||||
[InlineData("1test 2", "0000000001test 0000000002")]
|
||||
public void BaseItem_ModifySortChunks_Valid(string input, string expected)
|
||||
{
|
||||
Path = primaryPath
|
||||
};
|
||||
Assert.Equal(expected, BaseItem.ModifySortChunks(input));
|
||||
}
|
||||
|
||||
var videoAlt = new Video()
|
||||
/// <summary>
|
||||
/// Tests that GetMediaSourceName returns the correct name for media sources.
|
||||
/// </summary>
|
||||
/// <param name="primaryPath">The primary path.</param>
|
||||
/// <param name="altPath">The alternate path.</param>
|
||||
/// <param name="name">The expected name for the primary path.</param>
|
||||
/// <param name="altName">The expected name for the alternate path.</param>
|
||||
[Theory]
|
||||
[InlineData("/Movies/Ted/Ted.mp4", "/Movies/Ted/Ted - Unrated Edition.mp4", "Ted", "Unrated Edition")]
|
||||
[InlineData("/Movies/Deadpool 2 (2018)/Deadpool 2 (2018).mkv", "/Movies/Deadpool 2 (2018)/Deadpool 2 (2018) - Super Duper Cut.mkv", "Deadpool 2 (2018)", "Super Duper Cut")]
|
||||
public void GetMediaSourceName_Valid(string primaryPath, string altPath, string name, string altName)
|
||||
{
|
||||
Path = altPath,
|
||||
};
|
||||
Mock<IMediaSourceManager> mediaSourceManager = new();
|
||||
_ = mediaSourceManager.Setup(x => x.GetPathProtocol(It.IsAny<string>()))
|
||||
.Returns((string x) => MediaProtocol.File);
|
||||
BaseItem.MediaSourceManager = mediaSourceManager.Object;
|
||||
|
||||
video.LocalAlternateVersions = [videoAlt.Path];
|
||||
Video video = new()
|
||||
{
|
||||
Path = primaryPath,
|
||||
};
|
||||
|
||||
Assert.Equal(name, video.GetMediaSourceName(video));
|
||||
Assert.Equal(altName, video.GetMediaSourceName(videoAlt));
|
||||
Video videoAlt = new()
|
||||
{
|
||||
Path = altPath,
|
||||
};
|
||||
|
||||
video.LocalAlternateVersions = [videoAlt.Path];
|
||||
|
||||
Assert.Equal(name, video.GetMediaSourceName(video));
|
||||
Assert.Equal(altName, video.GetMediaSourceName(videoAlt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{462584F7-5023-4019-9EAC-B98CA458C0A0}</ProjectGuid>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "rCzbZx73oRU=",
|
||||
"dgSpecHash": "GDKjP2XUxGM=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\tests\\Jellyfin.Controller.Tests\\Jellyfin.Controller.Tests.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -8,19 +8,33 @@ namespace Jellyfin.Extensions.Tests
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for CopyTo extension methods.
|
||||
/// </summary>
|
||||
public static class CopyToExtensionsTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets test data for valid CopyTo operations.
|
||||
/// </summary>
|
||||
/// <returns>The test data.</returns>
|
||||
public static TheoryData<IReadOnlyList<int>, IList<int>, int, IList<int>> CopyTo_Valid_Correct_TestData()
|
||||
{
|
||||
var data = new TheoryData<IReadOnlyList<int>, IList<int>, int, IList<int>>
|
||||
TheoryData<IReadOnlyList<int>, IList<int>, int, IList<int>> data = new TheoryData<IReadOnlyList<int>, IList<int>, int, IList<int>>
|
||||
{
|
||||
{ new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 0, new[] { 0, 1, 2, 3, 4, 5 } },
|
||||
{ new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 2, new[] { 5, 4, 0, 1, 2, 0 } }
|
||||
{ new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 2, new[] { 5, 4, 0, 1, 2, 0 } },
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that CopyTo correctly copies elements.
|
||||
/// </summary>
|
||||
/// <param name="source">The source list.</param>
|
||||
/// <param name="destination">The destination list.</param>
|
||||
/// <param name="index">The starting index.</param>
|
||||
/// <param name="expected">The expected result.</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(CopyTo_Valid_Correct_TestData))]
|
||||
public static void CopyTo_Valid_Correct(IReadOnlyList<int> source, IList<int> destination, int index, IList<int> expected)
|
||||
@@ -29,20 +43,30 @@ namespace Jellyfin.Extensions.Tests
|
||||
Assert.Equal(expected, destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets test data for invalid CopyTo operations that should throw.
|
||||
/// </summary>
|
||||
/// <returns>The test data.</returns>
|
||||
public static TheoryData<IReadOnlyList<int>, IList<int>, int> CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData()
|
||||
{
|
||||
var data = new TheoryData<IReadOnlyList<int>, IList<int>, int>
|
||||
TheoryData<IReadOnlyList<int>, IList<int>, int> data = new TheoryData<IReadOnlyList<int>, IList<int>, int>
|
||||
{
|
||||
{ new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, -1 },
|
||||
{ new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 6 },
|
||||
{ new[] { 0, 1, 2 }, Array.Empty<int>(), 0 },
|
||||
{ new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0 }, 0 },
|
||||
{ new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 1 }
|
||||
{ new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 1 },
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that CopyTo throws ArgumentOutOfRangeException for invalid inputs.
|
||||
/// </summary>
|
||||
/// <param name="source">The source list.</param>
|
||||
/// <param name="destination">The destination list.</param>
|
||||
/// <param name="index">The starting index.</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData))]
|
||||
public static void CopyTo_Invalid_ThrowsArgumentOutOfRangeException(IReadOnlyList<int> source, IList<int> destination, int index)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);CS1591;CS1570;CS8600;SA1600;SA1309;SA1200</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -11,16 +11,24 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
using Jellyfin.Extensions.Json.Converters;
|
||||
using Xunit;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the JsonBoolNumberConverter.
|
||||
/// </summary>
|
||||
public class JsonBoolNumberTests
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions()
|
||||
private readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
Converters =
|
||||
{
|
||||
new JsonBoolNumberConverter()
|
||||
}
|
||||
new JsonBoolNumberConverter(),
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Tests deserialization of numbers to boolean.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string.</param>
|
||||
/// <param name="output">The expected output.</param>
|
||||
[Theory]
|
||||
[InlineData("1", true)]
|
||||
[InlineData("0", false)]
|
||||
@@ -29,21 +37,33 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
[InlineData("false", false)]
|
||||
public void Deserialize_Number_Valid_Success(string input, bool? output)
|
||||
{
|
||||
var value = JsonSerializer.Deserialize<bool>(input, _jsonOptions);
|
||||
bool value = JsonSerializer.Deserialize<bool>(input, this._jsonOptions);
|
||||
Assert.Equal(value, output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests serialization of boolean values.
|
||||
/// </summary>
|
||||
/// <param name="input">The input boolean.</param>
|
||||
/// <param name="output">The expected output.</param>
|
||||
[Theory]
|
||||
[InlineData(true, "true")]
|
||||
[InlineData(false, "false")]
|
||||
public void Serialize_Bool_Success(bool input, string output)
|
||||
{
|
||||
var value = JsonSerializer.Serialize(input, _jsonOptions);
|
||||
string value = JsonSerializer.Serialize(input, this._jsonOptions);
|
||||
Assert.Equal(value, output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Property test that non-zero integers deserialize to true.
|
||||
/// </summary>
|
||||
/// <param name="input">The input non-zero integer.</param>
|
||||
/// <returns>A property test result.</returns>
|
||||
[Property]
|
||||
public Property Deserialize_NonZeroInt_True(NonZeroInt input)
|
||||
=> JsonSerializer.Deserialize<bool>(input.ToString(), _jsonOptions).ToProperty();
|
||||
{
|
||||
return JsonSerializer.Deserialize<bool>(input.ToString(), this._jsonOptions).ToProperty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
{
|
||||
Converters =
|
||||
{
|
||||
new JsonBoolStringConverter()
|
||||
}
|
||||
new JsonBoolStringConverter(),
|
||||
},
|
||||
};
|
||||
|
||||
[Theory]
|
||||
@@ -23,7 +23,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
[InlineData(@"{ ""Value"": ""false"" }", false)]
|
||||
public void Deserialize_String_Valid_Success(string input, bool output)
|
||||
{
|
||||
var s = JsonSerializer.Deserialize<TestStruct>(input, _jsonOptions);
|
||||
TestStruct s = JsonSerializer.Deserialize<TestStruct>(input, this._jsonOptions);
|
||||
Assert.Equal(s.Value, output);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
[InlineData(false, "false")]
|
||||
public void Serialize_Bool_Success(bool input, string output)
|
||||
{
|
||||
var value = JsonSerializer.Serialize(input, _jsonOptions);
|
||||
string value = JsonSerializer.Serialize(input, this._jsonOptions);
|
||||
Assert.Equal(value, output);
|
||||
}
|
||||
|
||||
|
||||
+100
-50
@@ -7,205 +7,255 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Jellyfin.Extensions.Tests.Json.Models;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Xunit;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for JSON comma delimited collection converter.
|
||||
/// </summary>
|
||||
public class JsonCommaDelimitedCollectionTests
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions()
|
||||
private readonly JsonSerializerOptions jsonOptions = new()
|
||||
{
|
||||
Converters =
|
||||
{
|
||||
new JsonStringEnumConverter()
|
||||
}
|
||||
new JsonStringEnumConverter(),
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing null string value succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_String_Null_Success()
|
||||
{
|
||||
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": null }", _jsonOptions);
|
||||
GenericBodyArrayModel<string>? value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": null }", this.jsonOptions);
|
||||
Assert.Null(value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing empty string succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_Empty_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyArrayModel<string>
|
||||
GenericBodyArrayModel<string> desiredValue = new()
|
||||
{
|
||||
Value = Array.Empty<string>()
|
||||
Value = [],
|
||||
};
|
||||
|
||||
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": """" }", _jsonOptions);
|
||||
GenericBodyArrayModel<string>? value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": """" }", this.jsonOptions);
|
||||
Assert.Equal(desiredValue.Value, value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing empty string to list throws exception.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_EmptyList_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyListModel<string>
|
||||
GenericBodyListModel<string> desiredValue = new()
|
||||
{
|
||||
Value = []
|
||||
Value = [],
|
||||
};
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<GenericBodyListModel<string>>(@"{ ""Value"": """" }", _jsonOptions));
|
||||
_ = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<GenericBodyListModel<string>>(@"{ ""Value"": """" }", this.jsonOptions));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing empty string to IReadOnlyList succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_EmptyIReadOnlyList_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyIReadOnlyListModel<string>
|
||||
GenericBodyIReadOnlyListModel<string> desiredValue = new()
|
||||
{
|
||||
Value = []
|
||||
Value = [],
|
||||
};
|
||||
|
||||
var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": """" }", _jsonOptions);
|
||||
GenericBodyIReadOnlyListModel<string>? value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": """" }", this.jsonOptions);
|
||||
Assert.Equal(desiredValue.Value, value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing comma-delimited string succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_String_Valid_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyArrayModel<string>
|
||||
GenericBodyArrayModel<string> desiredValue = new()
|
||||
{
|
||||
Value = ["a", "b", "c"]
|
||||
Value = ["a", "b", "c"],
|
||||
};
|
||||
|
||||
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": ""a,b,c"" }", _jsonOptions);
|
||||
GenericBodyArrayModel<string>? value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": ""a,b,c"" }", this.jsonOptions);
|
||||
Assert.Equal(desiredValue.Value, value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing comma-delimited string to list throws exception.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_StringList_Valid_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyListModel<string>
|
||||
GenericBodyListModel<string> desiredValue = new()
|
||||
{
|
||||
Value = ["a", "b", "c"]
|
||||
Value = ["a", "b", "c"],
|
||||
};
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<GenericBodyListModel<string>>(@"{ ""Value"": ""a,b,c"" }", _jsonOptions));
|
||||
_ = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<GenericBodyListModel<string>>(@"{ ""Value"": ""a,b,c"" }", this.jsonOptions));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing comma-delimited string with spaces succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_String_Space_Valid_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyArrayModel<string>
|
||||
GenericBodyArrayModel<string> desiredValue = new()
|
||||
{
|
||||
Value = ["a", "b", "c"]
|
||||
Value = ["a", "b", "c"],
|
||||
};
|
||||
|
||||
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": ""a, b, c"" }", _jsonOptions);
|
||||
GenericBodyArrayModel<string>? value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": ""a, b, c"" }", this.jsonOptions);
|
||||
Assert.Equal(desiredValue.Value, value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing comma-delimited enum string succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_GenericCommandType_Valid_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
|
||||
GenericBodyArrayModel<GeneralCommandType> desiredValue = new()
|
||||
{
|
||||
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown]
|
||||
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown],
|
||||
};
|
||||
|
||||
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", _jsonOptions);
|
||||
GenericBodyArrayModel<GeneralCommandType>? value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", this.jsonOptions);
|
||||
Assert.Equal(desiredValue.Value, value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing comma-delimited enum string with empty entry succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_GenericCommandType_EmptyEntry_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
|
||||
GenericBodyArrayModel<GeneralCommandType> desiredValue = new()
|
||||
{
|
||||
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown]
|
||||
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown],
|
||||
};
|
||||
|
||||
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,,MoveDown"" }", _jsonOptions);
|
||||
GenericBodyArrayModel<GeneralCommandType>? value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,,MoveDown"" }", this.jsonOptions);
|
||||
Assert.Equal(desiredValue.Value, value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing comma-delimited enum string with invalid value succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_GenericCommandType_Invalid_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
|
||||
GenericBodyArrayModel<GeneralCommandType> desiredValue = new()
|
||||
{
|
||||
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown]
|
||||
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown],
|
||||
};
|
||||
|
||||
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,TotallyNotAValidCommand,MoveDown"" }", _jsonOptions);
|
||||
GenericBodyArrayModel<GeneralCommandType>? value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,TotallyNotAValidCommand,MoveDown"" }", this.jsonOptions);
|
||||
Assert.Equal(desiredValue.Value, value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing comma-delimited enum string with spaces succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_GenericCommandType_Space_Valid_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
|
||||
GenericBodyArrayModel<GeneralCommandType> desiredValue = new()
|
||||
{
|
||||
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown]
|
||||
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown],
|
||||
};
|
||||
|
||||
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", _jsonOptions);
|
||||
GenericBodyArrayModel<GeneralCommandType>? value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", this.jsonOptions);
|
||||
Assert.Equal(desiredValue.Value, value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing JSON array of strings succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_String_Array_Valid_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyArrayModel<string>
|
||||
GenericBodyArrayModel<string> desiredValue = new()
|
||||
{
|
||||
Value = ["a", "b", "c"]
|
||||
Value = ["a", "b", "c"],
|
||||
};
|
||||
|
||||
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", _jsonOptions);
|
||||
GenericBodyArrayModel<string>? value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", this.jsonOptions);
|
||||
Assert.Equal(desiredValue.Value, value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing JSON array of enums succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_GenericCommandType_Array_Valid_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
|
||||
GenericBodyArrayModel<GeneralCommandType> desiredValue = new()
|
||||
{
|
||||
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown]
|
||||
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown],
|
||||
};
|
||||
|
||||
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", _jsonOptions);
|
||||
GenericBodyArrayModel<GeneralCommandType>? value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", this.jsonOptions);
|
||||
Assert.Equal(desiredValue.Value, value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that serializing readonly collection succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Serialize_GenericCommandType_ReadOnlyArray_Valid_Success()
|
||||
{
|
||||
var valueToSerialize = new GenericBodyIReadOnlyCollectionModel<GeneralCommandType>
|
||||
GenericBodyIReadOnlyCollectionModel<GeneralCommandType> valueToSerialize = new()
|
||||
{
|
||||
Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }.AsReadOnly()
|
||||
Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }.AsReadOnly(),
|
||||
};
|
||||
|
||||
string value = JsonSerializer.Serialize<GenericBodyIReadOnlyCollectionModel<GeneralCommandType>>(valueToSerialize, _jsonOptions);
|
||||
string value = JsonSerializer.Serialize<GenericBodyIReadOnlyCollectionModel<GeneralCommandType>>(valueToSerialize, this.jsonOptions);
|
||||
Assert.Equal(@"{""Value"":[""MoveUp"",""MoveDown""]}", value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that serializing immutable array succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Serialize_GenericCommandType_ImmutableArrayArray_Valid_Success()
|
||||
{
|
||||
var valueToSerialize = new GenericBodyIReadOnlyCollectionModel<GeneralCommandType>
|
||||
GenericBodyIReadOnlyCollectionModel<GeneralCommandType> valueToSerialize = new()
|
||||
{
|
||||
Value = ImmutableArray.Create(new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown })
|
||||
Value = ImmutableArray.Create([GeneralCommandType.MoveUp, GeneralCommandType.MoveDown]),
|
||||
};
|
||||
|
||||
string value = JsonSerializer.Serialize<GenericBodyIReadOnlyCollectionModel<GeneralCommandType>>(valueToSerialize, _jsonOptions);
|
||||
string value = JsonSerializer.Serialize<GenericBodyIReadOnlyCollectionModel<GeneralCommandType>>(valueToSerialize, this.jsonOptions);
|
||||
Assert.Equal(@"{""Value"":[""MoveUp"",""MoveDown""]}", value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that serializing list succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Serialize_GenericCommandType_List_Valid_Success()
|
||||
{
|
||||
var valueToSerialize = new GenericBodyIReadOnlyListModel<GeneralCommandType>
|
||||
GenericBodyIReadOnlyListModel<GeneralCommandType> valueToSerialize = new()
|
||||
{
|
||||
Value = new List<GeneralCommandType> { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
|
||||
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown],
|
||||
};
|
||||
|
||||
string value = JsonSerializer.Serialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(valueToSerialize, _jsonOptions);
|
||||
string value = JsonSerializer.Serialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(valueToSerialize, this.jsonOptions);
|
||||
Assert.Equal(@"{""Value"":[""MoveUp"",""MoveDown""]}", value);
|
||||
}
|
||||
}
|
||||
|
||||
+48
-25
@@ -4,104 +4,127 @@
|
||||
|
||||
namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Jellyfin.Extensions.Tests.Json.Models;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Xunit;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for JSON comma delimited IReadOnlyList converter.
|
||||
/// </summary>
|
||||
public class JsonCommaDelimitedIReadOnlyListTests
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions()
|
||||
private readonly JsonSerializerOptions jsonOptions = new()
|
||||
{
|
||||
Converters =
|
||||
{
|
||||
new JsonStringEnumConverter()
|
||||
}
|
||||
new JsonStringEnumConverter(),
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing comma-delimited string succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_String_Valid_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyIReadOnlyListModel<string>
|
||||
GenericBodyIReadOnlyListModel<string> desiredValue = new()
|
||||
{
|
||||
Value = new[] { "a", "b", "c" }
|
||||
Value = ["a", "b", "c"],
|
||||
};
|
||||
|
||||
var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": ""a,b,c"" }", _jsonOptions);
|
||||
GenericBodyIReadOnlyListModel<string>? value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": ""a,b,c"" }", this.jsonOptions);
|
||||
Assert.Equal(desiredValue.Value, value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing comma-delimited string with spaces succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_String_Space_Valid_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyIReadOnlyListModel<string>
|
||||
GenericBodyIReadOnlyListModel<string> desiredValue = new()
|
||||
{
|
||||
Value = new[] { "a", "b", "c" }
|
||||
Value = ["a", "b", "c"],
|
||||
};
|
||||
|
||||
var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": ""a, b, c"" }", _jsonOptions);
|
||||
GenericBodyIReadOnlyListModel<string>? value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": ""a, b, c"" }", this.jsonOptions);
|
||||
Assert.Equal(desiredValue.Value, value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing comma-delimited enum string succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_GenericCommandType_Valid_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyIReadOnlyListModel<GeneralCommandType>
|
||||
GenericBodyIReadOnlyListModel<GeneralCommandType> desiredValue = new()
|
||||
{
|
||||
Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
|
||||
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown],
|
||||
};
|
||||
|
||||
var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", _jsonOptions);
|
||||
GenericBodyIReadOnlyListModel<GeneralCommandType>? value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", this.jsonOptions);
|
||||
Assert.Equal(desiredValue.Value, value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing comma-delimited enum string with spaces succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_GenericCommandType_Space_Valid_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyIReadOnlyListModel<GeneralCommandType>
|
||||
GenericBodyIReadOnlyListModel<GeneralCommandType> desiredValue = new()
|
||||
{
|
||||
Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
|
||||
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown],
|
||||
};
|
||||
|
||||
var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", _jsonOptions);
|
||||
GenericBodyIReadOnlyListModel<GeneralCommandType>? value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", this.jsonOptions);
|
||||
Assert.Equal(desiredValue.Value, value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing JSON array of strings succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_String_Array_Valid_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyIReadOnlyListModel<string>
|
||||
GenericBodyIReadOnlyListModel<string> desiredValue = new()
|
||||
{
|
||||
Value = new[] { "a", "b", "c" }
|
||||
Value = ["a", "b", "c"],
|
||||
};
|
||||
|
||||
var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", _jsonOptions);
|
||||
GenericBodyIReadOnlyListModel<string>? value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", this.jsonOptions);
|
||||
Assert.Equal(desiredValue.Value, value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deserializing JSON array of enums succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Deserialize_GenericCommandType_Array_Valid_Success()
|
||||
{
|
||||
var desiredValue = new GenericBodyIReadOnlyListModel<GeneralCommandType>
|
||||
GenericBodyIReadOnlyListModel<GeneralCommandType> desiredValue = new()
|
||||
{
|
||||
Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
|
||||
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown],
|
||||
};
|
||||
|
||||
var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", _jsonOptions);
|
||||
GenericBodyIReadOnlyListModel<GeneralCommandType>? value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", this.jsonOptions);
|
||||
Assert.Equal(desiredValue.Value, value?.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that serializing IReadOnlyList succeeds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Serialize_GenericCommandType_IReadOnlyList_Valid_Success()
|
||||
{
|
||||
var valueToSerialize = new GenericBodyIReadOnlyListModel<GeneralCommandType>
|
||||
GenericBodyIReadOnlyListModel<GeneralCommandType> valueToSerialize = new()
|
||||
{
|
||||
Value = new List<GeneralCommandType> { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
|
||||
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown],
|
||||
};
|
||||
|
||||
string value = JsonSerializer.Serialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(valueToSerialize, _jsonOptions);
|
||||
string value = JsonSerializer.Serialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(valueToSerialize, this.jsonOptions);
|
||||
Assert.Equal(@"{""Value"":[""MoveUp"",""MoveDown""]}", value);
|
||||
}
|
||||
}
|
||||
|
||||
+98
-97
@@ -2,115 +2,116 @@
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace Jellyfin.Extensions.Tests.Json.Converters;
|
||||
|
||||
using System.Text.Json;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions.Json.Converters;
|
||||
using Xunit;
|
||||
|
||||
public class JsonDefaultStringEnumConverterTests
|
||||
namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonOptions = new() { Converters = { new JsonDefaultStringEnumConverterFactory() } };
|
||||
using System.Text.Json;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions.Json.Converters;
|
||||
using Xunit;
|
||||
|
||||
/// <summary>
|
||||
/// Test to ensure that `null` and empty string are deserialized to the default value.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string.</param>
|
||||
/// <param name="output">The expected enum value.</param>
|
||||
[Theory]
|
||||
[InlineData("\"\"", MediaStreamProtocol.http)]
|
||||
[InlineData("\"Http\"", MediaStreamProtocol.http)]
|
||||
[InlineData("\"Hls\"", MediaStreamProtocol.hls)]
|
||||
public void Deserialize_Enum_Direct(string input, MediaStreamProtocol output)
|
||||
public class JsonDefaultStringEnumConverterTests
|
||||
{
|
||||
var value = JsonSerializer.Deserialize<MediaStreamProtocol>(input, _jsonOptions);
|
||||
Assert.Equal(output, value);
|
||||
}
|
||||
private readonly JsonSerializerOptions _jsonOptions = new() { Converters = { new JsonDefaultStringEnumConverterFactory() } };
|
||||
|
||||
/// <summary>
|
||||
/// Test to ensure that `null` and empty string are deserialized to the default value.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string.</param>
|
||||
/// <param name="output">The expected enum value.</param>
|
||||
[Theory]
|
||||
[InlineData(null, MediaStreamProtocol.http)]
|
||||
[InlineData("\"\"", MediaStreamProtocol.http)]
|
||||
[InlineData("\"Http\"", MediaStreamProtocol.http)]
|
||||
[InlineData("\"Hls\"", MediaStreamProtocol.hls)]
|
||||
public void Deserialize_Enum(string? input, MediaStreamProtocol output)
|
||||
{
|
||||
input ??= "null";
|
||||
var json = $"{{ \"EnumValue\": {input} }}";
|
||||
var value = JsonSerializer.Deserialize<TestClass>(json, _jsonOptions);
|
||||
Assert.NotNull(value);
|
||||
Assert.Equal(output, value.EnumValue);
|
||||
}
|
||||
/// <summary>
|
||||
/// Test to ensure that `null` and empty string are deserialized to the default value.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string.</param>
|
||||
/// <param name="output">The expected enum value.</param>
|
||||
[Theory]
|
||||
[InlineData("\"\"", MediaStreamProtocol.http)]
|
||||
[InlineData("\"Http\"", MediaStreamProtocol.http)]
|
||||
[InlineData("\"Hls\"", MediaStreamProtocol.hls)]
|
||||
public void Deserialize_Enum_Direct(string input, MediaStreamProtocol output)
|
||||
{
|
||||
MediaStreamProtocol value = JsonSerializer.Deserialize<MediaStreamProtocol>(input, this._jsonOptions);
|
||||
Assert.Equal(output, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test to ensure that empty string is deserialized to the default value,
|
||||
/// and `null` is deserialized to `null`.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string.</param>
|
||||
/// <param name="output">The expected enum value.</param>
|
||||
[Theory]
|
||||
[InlineData(null, null)]
|
||||
[InlineData("\"\"", MediaStreamProtocol.http)]
|
||||
[InlineData("\"Http\"", MediaStreamProtocol.http)]
|
||||
[InlineData("\"Hls\"", MediaStreamProtocol.hls)]
|
||||
public void Deserialize_Enum_Nullable(string? input, MediaStreamProtocol? output)
|
||||
{
|
||||
input ??= "null";
|
||||
var json = $"{{ \"EnumValue\": {input} }}";
|
||||
var value = JsonSerializer.Deserialize<NullTestClass>(json, _jsonOptions);
|
||||
Assert.NotNull(value);
|
||||
Assert.Equal(output, value.EnumValue);
|
||||
}
|
||||
/// <summary>
|
||||
/// Test to ensure that `null` and empty string are deserialized to the default value.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string.</param>
|
||||
/// <param name="output">The expected enum value.</param>
|
||||
[Theory]
|
||||
[InlineData(null, MediaStreamProtocol.http)]
|
||||
[InlineData("\"\"", MediaStreamProtocol.http)]
|
||||
[InlineData("\"Http\"", MediaStreamProtocol.http)]
|
||||
[InlineData("\"Hls\"", MediaStreamProtocol.hls)]
|
||||
public void Deserialize_Enum(string? input, MediaStreamProtocol output)
|
||||
{
|
||||
input ??= "null";
|
||||
string json = $"{{ \"EnumValue\": {input} }}";
|
||||
TestClass value = JsonSerializer.Deserialize<TestClass>(json, this._jsonOptions);
|
||||
Assert.NotNull(value);
|
||||
Assert.Equal(output, value.EnumValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the roundtrip serialization & deserialization is successful.
|
||||
/// </summary>
|
||||
/// <param name="input">Input enum.</param>
|
||||
/// <param name="output">Output enum.</param>
|
||||
[Theory]
|
||||
[InlineData(MediaStreamProtocol.http, MediaStreamProtocol.http)]
|
||||
[InlineData(MediaStreamProtocol.hls, MediaStreamProtocol.hls)]
|
||||
public void Enum_RoundTrip(MediaStreamProtocol input, MediaStreamProtocol output)
|
||||
{
|
||||
var inputObj = new TestClass { EnumValue = input };
|
||||
/// <summary>
|
||||
/// Test to ensure that empty string is deserialized to the default value,
|
||||
/// and `null` is deserialized to `null`.
|
||||
/// </summary>
|
||||
/// <param name="input">The input string.</param>
|
||||
/// <param name="output">The expected enum value.</param>
|
||||
[Theory]
|
||||
[InlineData(null, null)]
|
||||
[InlineData("\"\"", MediaStreamProtocol.http)]
|
||||
[InlineData("\"Http\"", MediaStreamProtocol.http)]
|
||||
[InlineData("\"Hls\"", MediaStreamProtocol.hls)]
|
||||
public void Deserialize_Enum_Nullable(string? input, MediaStreamProtocol? output)
|
||||
{
|
||||
input ??= "null";
|
||||
string json = $"{{ \"EnumValue\": {input} }}";
|
||||
NullTestClass value = JsonSerializer.Deserialize<NullTestClass>(json, this._jsonOptions);
|
||||
Assert.NotNull(value);
|
||||
Assert.Equal(output, value.EnumValue);
|
||||
}
|
||||
|
||||
var outputObj = JsonSerializer.Deserialize<TestClass>(JsonSerializer.Serialize(inputObj, _jsonOptions), _jsonOptions);
|
||||
/// <summary>
|
||||
/// Ensures that the roundtrip serialization & deserialization is successful.
|
||||
/// </summary>
|
||||
/// <param name="input">Input enum.</param>
|
||||
/// <param name="output">Output enum.</param>
|
||||
[Theory]
|
||||
[InlineData(MediaStreamProtocol.http, MediaStreamProtocol.http)]
|
||||
[InlineData(MediaStreamProtocol.hls, MediaStreamProtocol.hls)]
|
||||
public void Enum_RoundTrip(MediaStreamProtocol input, MediaStreamProtocol output)
|
||||
{
|
||||
TestClass inputObj = new() { EnumValue = input };
|
||||
|
||||
Assert.NotNull(outputObj);
|
||||
Assert.Equal(output, outputObj.EnumValue);
|
||||
}
|
||||
TestClass? outputObj = JsonSerializer.Deserialize<TestClass>(JsonSerializer.Serialize(inputObj, this._jsonOptions), this._jsonOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the roundtrip serialization & deserialization is successful, including null.
|
||||
/// </summary>
|
||||
/// <param name="input">Input enum.</param>
|
||||
/// <param name="output">Output enum.</param>
|
||||
[Theory]
|
||||
[InlineData(MediaStreamProtocol.http, MediaStreamProtocol.http)]
|
||||
[InlineData(MediaStreamProtocol.hls, MediaStreamProtocol.hls)]
|
||||
[InlineData(null, null)]
|
||||
public void Enum_RoundTrip_Nullable(MediaStreamProtocol? input, MediaStreamProtocol? output)
|
||||
{
|
||||
var inputObj = new NullTestClass { EnumValue = input };
|
||||
Assert.NotNull(outputObj);
|
||||
Assert.Equal(output, outputObj.EnumValue);
|
||||
}
|
||||
|
||||
var outputObj = JsonSerializer.Deserialize<NullTestClass>(JsonSerializer.Serialize(inputObj, _jsonOptions), _jsonOptions);
|
||||
/// <summary>
|
||||
/// Ensures that the roundtrip serialization & deserialization is successful, including null.
|
||||
/// </summary>
|
||||
/// <param name="input">Input enum.</param>
|
||||
/// <param name="output">Output enum.</param>
|
||||
[Theory]
|
||||
[InlineData(MediaStreamProtocol.http, MediaStreamProtocol.http)]
|
||||
[InlineData(MediaStreamProtocol.hls, MediaStreamProtocol.hls)]
|
||||
[InlineData(null, null)]
|
||||
public void Enum_RoundTrip_Nullable(MediaStreamProtocol? input, MediaStreamProtocol? output)
|
||||
{
|
||||
NullTestClass inputObj = new() { EnumValue = input };
|
||||
|
||||
Assert.NotNull(outputObj);
|
||||
Assert.Equal(output, outputObj.EnumValue);
|
||||
}
|
||||
NullTestClass? outputObj = JsonSerializer.Deserialize<NullTestClass>(JsonSerializer.Serialize(inputObj, this._jsonOptions), this._jsonOptions);
|
||||
|
||||
private sealed class TestClass
|
||||
{
|
||||
public MediaStreamProtocol EnumValue { get; set; }
|
||||
}
|
||||
Assert.NotNull(outputObj);
|
||||
Assert.Equal(output, outputObj.EnumValue);
|
||||
}
|
||||
|
||||
private sealed class NullTestClass
|
||||
{
|
||||
public MediaStreamProtocol? EnumValue { get; set; }
|
||||
private sealed class TestClass
|
||||
{
|
||||
public MediaStreamProtocol EnumValue { get; set; }
|
||||
}
|
||||
|
||||
private sealed class NullTestClass
|
||||
{
|
||||
public MediaStreamProtocol? EnumValue { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,31 +2,32 @@
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace Jellyfin.Extensions.Tests.Json.Converters;
|
||||
|
||||
using System.Text.Json;
|
||||
using Jellyfin.Extensions.Json.Converters;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Xunit;
|
||||
|
||||
public class JsonFlagEnumTests
|
||||
namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonOptions = new()
|
||||
using System.Text.Json;
|
||||
using Jellyfin.Extensions.Json.Converters;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Xunit;
|
||||
|
||||
public class JsonFlagEnumTests
|
||||
{
|
||||
Converters =
|
||||
private readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
new JsonFlagEnumConverter<TranscodeReason>()
|
||||
Converters =
|
||||
{
|
||||
new JsonFlagEnumConverter<TranscodeReason>(),
|
||||
},
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[InlineData(TranscodeReason.AudioIsExternal | TranscodeReason.ContainerNotSupported, "[\"ContainerNotSupported\",\"AudioIsExternal\"]")]
|
||||
[InlineData(TranscodeReason.AudioIsExternal | TranscodeReason.ContainerNotSupported | TranscodeReason.VideoBitDepthNotSupported, "[\"ContainerNotSupported\",\"AudioIsExternal\",\"VideoBitDepthNotSupported\"]")]
|
||||
[InlineData((TranscodeReason)0, "[]")]
|
||||
public void Serialize_Transcode_Reason(TranscodeReason transcodeReason, string output)
|
||||
{
|
||||
string result = JsonSerializer.Serialize(transcodeReason, this._jsonOptions);
|
||||
|
||||
Assert.Equal(output, result);
|
||||
}
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[InlineData(TranscodeReason.AudioIsExternal | TranscodeReason.ContainerNotSupported, "[\"ContainerNotSupported\",\"AudioIsExternal\"]")]
|
||||
[InlineData(TranscodeReason.AudioIsExternal | TranscodeReason.ContainerNotSupported | TranscodeReason.VideoBitDepthNotSupported, "[\"ContainerNotSupported\",\"AudioIsExternal\",\"VideoBitDepthNotSupported\"]")]
|
||||
[InlineData((TranscodeReason)0, "[]")]
|
||||
public void Serialize_Transcode_Reason(TranscodeReason transcodeReason, string output)
|
||||
{
|
||||
var result = JsonSerializer.Serialize(transcodeReason, _jsonOptions);
|
||||
|
||||
Assert.Equal(output, result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,49 +15,49 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
|
||||
public JsonGuidConverterTests()
|
||||
{
|
||||
_options = new JsonSerializerOptions();
|
||||
_options.Converters.Add(new JsonGuidConverter());
|
||||
this._options = new JsonSerializerOptions();
|
||||
this._options.Converters.Add(new JsonGuidConverter());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_Valid_Success()
|
||||
{
|
||||
Guid value = JsonSerializer.Deserialize<Guid>(@"""a852a27afe324084ae66db579ee3ee18""", _options);
|
||||
Assert.Equal(new Guid("a852a27afe324084ae66db579ee3ee18"), value);
|
||||
Guid value = JsonSerializer.Deserialize<Guid>(@"""a852a27afe324084ae66db579ee3ee18""", this._options);
|
||||
Assert.Equal(new("a852a27afe324084ae66db579ee3ee18"), value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_ValidDashed_Success()
|
||||
{
|
||||
Guid value = JsonSerializer.Deserialize<Guid>(@"""e9b2dcaa-529c-426e-9433-5e9981f27f2e""", _options);
|
||||
Assert.Equal(new Guid("e9b2dcaa-529c-426e-9433-5e9981f27f2e"), value);
|
||||
Guid value = JsonSerializer.Deserialize<Guid>(@"""e9b2dcaa-529c-426e-9433-5e9981f27f2e""", this._options);
|
||||
Assert.Equal(new("e9b2dcaa-529c-426e-9433-5e9981f27f2e"), value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Roundtrip_Valid_Success()
|
||||
{
|
||||
Guid guid = new Guid("a852a27afe324084ae66db579ee3ee18");
|
||||
string value = JsonSerializer.Serialize(guid, _options);
|
||||
Assert.Equal(guid, JsonSerializer.Deserialize<Guid>(value, _options));
|
||||
Guid guid = new("a852a27afe324084ae66db579ee3ee18");
|
||||
string value = JsonSerializer.Serialize(guid, this._options);
|
||||
Assert.Equal(guid, JsonSerializer.Deserialize<Guid>(value, this._options));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_Null_EmptyGuid()
|
||||
{
|
||||
Assert.Equal(Guid.Empty, JsonSerializer.Deserialize<Guid>("null", _options));
|
||||
Assert.Equal(Guid.Empty, JsonSerializer.Deserialize<Guid>("null", this._options));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialize_EmptyGuid_EmptyGuid()
|
||||
{
|
||||
Assert.Equal($"\"{Guid.Empty:N}\"", JsonSerializer.Serialize(Guid.Empty, _options));
|
||||
Assert.Equal($"\"{Guid.Empty:N}\"", JsonSerializer.Serialize(Guid.Empty, this._options));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialize_Valid_NoDash_Success()
|
||||
{
|
||||
var guid = new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA");
|
||||
var str = JsonSerializer.Serialize(guid, _options);
|
||||
Guid guid = new("531797E9-9457-40E0-88BC-B1D6D38752FA");
|
||||
string str = JsonSerializer.Serialize(guid, this._options);
|
||||
Assert.Equal($"\"{guid:N}\"", str);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
public void Serialize_Nullable_Success()
|
||||
{
|
||||
Guid? guid = new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA");
|
||||
var str = JsonSerializer.Serialize(guid, _options);
|
||||
string str = JsonSerializer.Serialize(guid, this._options);
|
||||
Assert.Equal($"\"{guid:N}\"", str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,69 +15,69 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
|
||||
public JsonNullableGuidConverterTests()
|
||||
{
|
||||
_options = new JsonSerializerOptions();
|
||||
_options.Converters.Add(new JsonNullableGuidConverter());
|
||||
this._options = new JsonSerializerOptions();
|
||||
this._options.Converters.Add(new JsonNullableGuidConverter());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_Valid_Success()
|
||||
{
|
||||
Guid? value = JsonSerializer.Deserialize<Guid?>(@"""a852a27afe324084ae66db579ee3ee18""", _options);
|
||||
Assert.Equal(new Guid("a852a27afe324084ae66db579ee3ee18"), value);
|
||||
Guid? value = JsonSerializer.Deserialize<Guid?>(@"""a852a27afe324084ae66db579ee3ee18""", this._options);
|
||||
Assert.Equal(new("a852a27afe324084ae66db579ee3ee18"), value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_ValidDashed_Success()
|
||||
{
|
||||
Guid? value = JsonSerializer.Deserialize<Guid?>(@"""e9b2dcaa-529c-426e-9433-5e9981f27f2e""", _options);
|
||||
Assert.Equal(new Guid("e9b2dcaa-529c-426e-9433-5e9981f27f2e"), value);
|
||||
Guid? value = JsonSerializer.Deserialize<Guid?>(@"""e9b2dcaa-529c-426e-9433-5e9981f27f2e""", this._options);
|
||||
Assert.Equal(new("e9b2dcaa-529c-426e-9433-5e9981f27f2e"), value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Roundtrip_Valid_Success()
|
||||
{
|
||||
Guid guid = new Guid("a852a27afe324084ae66db579ee3ee18");
|
||||
string value = JsonSerializer.Serialize(guid, _options);
|
||||
Assert.Equal(guid, JsonSerializer.Deserialize<Guid?>(value, _options));
|
||||
Guid guid = new("a852a27afe324084ae66db579ee3ee18");
|
||||
string value = JsonSerializer.Serialize(guid, this._options);
|
||||
Assert.Equal(guid, JsonSerializer.Deserialize<Guid?>(value, this._options));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_Null_Null()
|
||||
{
|
||||
Assert.Null(JsonSerializer.Deserialize<Guid?>("null", _options));
|
||||
Assert.Null(JsonSerializer.Deserialize<Guid?>("null", this._options));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_EmptyGuid_EmptyGuid()
|
||||
{
|
||||
Assert.Equal(Guid.Empty, JsonSerializer.Deserialize<Guid?>(@"""00000000-0000-0000-0000-000000000000""", _options));
|
||||
Assert.Equal(Guid.Empty, JsonSerializer.Deserialize<Guid?>(@"""00000000-0000-0000-0000-000000000000""", this._options));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialize_EmptyGuid_Null()
|
||||
{
|
||||
Assert.Equal("null", JsonSerializer.Serialize((Guid?)Guid.Empty, _options));
|
||||
Assert.Equal("null", JsonSerializer.Serialize((Guid?)Guid.Empty, this._options));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialize_Null_Null()
|
||||
{
|
||||
Assert.Equal("null", JsonSerializer.Serialize((Guid?)null, _options));
|
||||
Assert.Equal("null", JsonSerializer.Serialize((Guid?)null, this._options));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialize_Valid_NoDash_Success()
|
||||
{
|
||||
var guid = (Guid?)new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA");
|
||||
var str = JsonSerializer.Serialize(guid, _options);
|
||||
Guid? guid = new("531797E9-9457-40E0-88BC-B1D6D38752FA");
|
||||
string str = JsonSerializer.Serialize(guid, this._options);
|
||||
Assert.Equal($"\"{guid:N}\"", str);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialize_Nullable_Success()
|
||||
{
|
||||
Guid? guid = new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA");
|
||||
var str = JsonSerializer.Serialize(guid, _options);
|
||||
Guid? guid = new("531797E9-9457-40E0-88BC-B1D6D38752FA");
|
||||
string str = JsonSerializer.Serialize(guid, this._options);
|
||||
Assert.Equal($"\"{guid:N}\"", str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
{
|
||||
Converters =
|
||||
{
|
||||
new JsonStringConverter()
|
||||
}
|
||||
new JsonStringConverter(),
|
||||
},
|
||||
};
|
||||
|
||||
[Theory]
|
||||
@@ -26,7 +26,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
[InlineData("false", "false")]
|
||||
public void Deserialize_String_Valid_Success(string input, string output)
|
||||
{
|
||||
var deserialized = JsonSerializer.Deserialize<string>(input, _jsonSerializerOptions);
|
||||
string? deserialized = JsonSerializer.Deserialize<string>(input, this._jsonSerializerOptions);
|
||||
Assert.Equal(deserialized, output);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
{
|
||||
const string? input = "123";
|
||||
const int output = 123;
|
||||
var deserialized = JsonSerializer.Deserialize<int>(input, _jsonSerializerOptions);
|
||||
int deserialized = JsonSerializer.Deserialize<int>(input, this._jsonSerializerOptions);
|
||||
Assert.Equal(output, deserialized);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,25 +15,25 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
|
||||
|
||||
public JsonVersionConverterTests()
|
||||
{
|
||||
_options = new JsonSerializerOptions();
|
||||
_options.Converters.Add(new JsonVersionConverter());
|
||||
this._options = new JsonSerializerOptions();
|
||||
this._options.Converters.Add(new JsonVersionConverter());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_Version_Success()
|
||||
{
|
||||
var input = "\"1.025.222\"";
|
||||
var output = new Version(1, 25, 222);
|
||||
var deserializedInput = JsonSerializer.Deserialize<Version>(input, _options);
|
||||
string input = "\"1.025.222\"";
|
||||
Version output = new(1, 25, 222);
|
||||
Version? deserializedInput = JsonSerializer.Deserialize<Version>(input, this._options);
|
||||
Assert.Equal(output, deserializedInput);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialize_Version_Success()
|
||||
{
|
||||
var input = new Version(1, 09, 59);
|
||||
var output = "\"1.9.59\"";
|
||||
var serializedInput = JsonSerializer.Serialize(input, _options);
|
||||
Version input = new(1, 09, 59);
|
||||
string output = "\"1.9.59\"";
|
||||
string serializedInput = JsonSerializer.Serialize(input, this._options);
|
||||
Assert.Equal(output, serializedInput);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "en/g4Rojfa8=",
|
||||
"dgSpecHash": "cBK/w0WVJEs=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\tests\\Jellyfin.Extensions.Tests\\Jellyfin.Extensions.Tests.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "YQ4opaxxEO8=",
|
||||
"dgSpecHash": "nbns8k6jJuA=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\tests\\Jellyfin.LiveTv.Tests\\Jellyfin.LiveTv.Tests.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "/lT1/xD3XnU=",
|
||||
"dgSpecHash": "hvFFds1OJpc=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\tests\\Jellyfin.MediaEncoding.Hls.Tests\\Jellyfin.MediaEncoding.Hls.Tests.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "0nvUEzLEN94=",
|
||||
"dgSpecHash": "5g3aj0khPqM=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\tests\\Jellyfin.MediaEncoding.Keyframes.Tests\\Jellyfin.MediaEncoding.Keyframes.Tests.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "SBmVy71j/UM=",
|
||||
"dgSpecHash": "2rGlge7UbGY=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\tests\\Jellyfin.MediaEncoding.Tests\\Jellyfin.MediaEncoding.Tests.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -53,8 +53,7 @@ namespace Jellyfin.Model.Tests.Cryptography
|
||||
new Dictionary<string, string>()
|
||||
{
|
||||
{ "iterations", "1000" },
|
||||
{ "m", "120" }
|
||||
}));
|
||||
{ "m", "120" },`r`n }));
|
||||
|
||||
// Id + hash
|
||||
data.Add(
|
||||
@@ -83,7 +82,8 @@ namespace Jellyfin.Model.Tests.Cryptography
|
||||
Array.Empty<byte>(),
|
||||
new Dictionary<string, string>()
|
||||
{
|
||||
{ "iterations", "1000" }
|
||||
{ "iterations", "1000" },
|
||||
{ "iterations", "1000" },
|
||||
}));
|
||||
// Id + parameters + hash
|
||||
data.Add(
|
||||
@@ -95,7 +95,7 @@ namespace Jellyfin.Model.Tests.Cryptography
|
||||
new Dictionary<string, string>()
|
||||
{
|
||||
{ "iterations", "1000" },
|
||||
{ "m", "120" }
|
||||
{ "m", "120" },
|
||||
}));
|
||||
// Id + parameters + salt + hash
|
||||
data.Add(
|
||||
@@ -107,7 +107,7 @@ namespace Jellyfin.Model.Tests.Cryptography
|
||||
new Dictionary<string, string>()
|
||||
{
|
||||
{ "iterations", "1000" },
|
||||
{ "m", "120" }
|
||||
{ "m", "120" },
|
||||
}));
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "S2M6GE9E6Mw=",
|
||||
"dgSpecHash": "E+3Xf4aE0A8=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\tests\\Jellyfin.Model.Tests\\Jellyfin.Model.Tests.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -29,6 +29,6 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
||||
var info1 = new AudioBookFileInfo(string.Empty, string.Empty);
|
||||
var info2 = new AudioBookFileInfo(string.Empty, string.Empty);
|
||||
Assert.Equal(0, info1.CompareTo(info2));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
||||
"Ready Player One (2020)/audiobook.mp3",
|
||||
"Ready Player One (2020)/extra.mp3",
|
||||
|
||||
".mp3"
|
||||
".mp3",
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
@@ -89,7 +89,7 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
||||
"Superman/extra.mp3",
|
||||
|
||||
"Batman/ Chapter 1 .mp3",
|
||||
"Batman/Chapter 1[loss-less].mp3"
|
||||
"Batman/Chapter 1[loss-less].mp3",
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
@@ -166,7 +166,7 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
||||
Name = " ",
|
||||
Path = " .mp3",
|
||||
Year = null
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
@@ -183,7 +183,7 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
||||
{
|
||||
Assert.Equal(data[i].Name, result[i].Name);
|
||||
Assert.Equal(data[i].Year, result[i].Year);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -192,7 +192,7 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
||||
var files = new[]
|
||||
{
|
||||
"Harry Potter and the Deathly Hallows/Chapter 1.ogg",
|
||||
"Harry Potter and the Deathly Hallows/Harry Potter and the Deathly Hallows.nfo"
|
||||
"Harry Potter and the Deathly Hallows/Harry Potter and the Deathly Hallows.nfo",
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
@@ -212,7 +212,7 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
||||
var files = new[]
|
||||
{
|
||||
"Harry Potter and the Deathly Hallows/Chapter 1.mp3",
|
||||
"Harry Potter and the Deathly Hallows/Harry Potter and the Deathly Hallows trailer.mp3"
|
||||
"Harry Potter and the Deathly Hallows/Harry Potter and the Deathly Hallows trailer.mp3",
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
@@ -231,7 +231,7 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
"Harry Potter and the Deathly Hallows trailer.mp3"
|
||||
"Harry Potter and the Deathly Hallows trailer.mp3",
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
@@ -271,6 +271,6 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
||||
public string Name;
|
||||
public string Path;
|
||||
public int? Year;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,6 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
||||
var result = new AudioBookResolver(_namingOptions).Resolve(string.Empty);
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,6 @@ namespace Jellyfin.Naming.Tests.Common
|
||||
exp.Expression = "test";
|
||||
Assert.Equal("test", exp.Expression);
|
||||
Assert.NotNull(exp.Regex);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,4 +122,4 @@ public class ExternalPathParserTests
|
||||
Assert.Equal(isForced, actual.IsForced);
|
||||
Assert.Equal(isHearingImpaired, actual.IsHearingImpaired);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,6 @@ namespace Jellyfin.Naming.Tests.Music
|
||||
var parser = new AlbumParser(_namingOptions);
|
||||
|
||||
Assert.Equal(result, parser.IsMultiPart(path));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,6 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
var result = _resolver.Resolve(path, false, null, null, true);
|
||||
|
||||
Assert.Equal(episodeNumber, result?.EpisodeNumber);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,6 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
Assert.Equal(month, result?.Month);
|
||||
Assert.Equal(day, result?.Day);
|
||||
Assert.Equal(seriesName, result?.SeriesName, true);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,6 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
.Parse(path, false);
|
||||
|
||||
Assert.Equal(expected, result.EpisodeNumber);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,6 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
var result = _resolver.Resolve(path, false);
|
||||
|
||||
Assert.Equal(episodeNumber, result?.EpisodeNumber);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,6 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
var res = p.Parse("ABC_2019_10_21 11:00:00", false);
|
||||
|
||||
Assert.True(res.Success);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,6 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
var result = _episodePathParser.Parse(filename, false);
|
||||
|
||||
Assert.Equal(result.EndingEpisodeNumber, endingEpisodeNumber);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,6 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
var result = _resolver.Resolve(path, false);
|
||||
|
||||
Assert.Equal(expected, result?.SeasonNumber);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,4 +87,4 @@ public class SeasonPathParserTests
|
||||
Assert.Equal(seasonNumber, result.SeasonNumber);
|
||||
Assert.Equal(isSeasonDirectory, result.IsSeasonFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,6 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
|
||||
Assert.Equal(name, res.SeriesName);
|
||||
Assert.True(res.Success);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,6 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
var res = SeriesResolver.Resolve(_namingOptions, path);
|
||||
|
||||
Assert.Equal(name, res.Name);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,6 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
Assert.Null(result!.StubType);
|
||||
Assert.Equal(episodeEndNumber, result!.EndingEpisodeNumber);
|
||||
Assert.False(result!.IsByDate);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,4 +32,4 @@ public class TvParserHelpersTest
|
||||
Assert.False(successful);
|
||||
Assert.Null(parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,6 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
|
||||
Assert.Equal(expectedName, result.Name, true);
|
||||
Assert.Equal(expectedYear, result.Year);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,6 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
{
|
||||
Assert.False(VideoResolver.TryCleanString(input, _namingOptions, out var newName));
|
||||
Assert.True(string.IsNullOrEmpty(newName));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,6 +166,6 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var res = ExtraRuleResolver.GetExtraInfo("extra.mp4", options);
|
||||
|
||||
Assert.Equal(rule, res.Rule);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
else
|
||||
{
|
||||
Assert.Equal(format3D, result.Format3D, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"/movies/X-Men Days of Future Past/X-Men Days of Future Past - 1080p.mkv",
|
||||
"/movies/X-Men Days of Future Past/X-Men Days of Future Past-trailer.mp4",
|
||||
"/movies/X-Men Days of Future Past/X-Men Days of Future Past - [hsbs].mkv",
|
||||
"/movies/X-Men Days of Future Past/X-Men Days of Future Past [hsbs].mkv"
|
||||
"/movies/X-Men Days of Future Past/X-Men Days of Future Past [hsbs].mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -41,7 +41,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"/movies/X-Men Days of Future Past/X-Men Days of Future Past - apple.mkv",
|
||||
"/movies/X-Men Days of Future Past/X-Men Days of Future Past-trailer.mp4",
|
||||
"/movies/X-Men Days of Future Past/X-Men Days of Future Past - banana.mkv",
|
||||
"/movies/X-Men Days of Future Past/X-Men Days of Future Past [banana].mp4"
|
||||
"/movies/X-Men Days of Future Past/X-Men Days of Future Past [banana].mp4",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -59,7 +59,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"/movies/The Phantom of the Opera (1925)/The Phantom of the Opera (1925) - 1925 version.mkv",
|
||||
"/movies/The Phantom of the Opera (1925)/The Phantom of the Opera (1925) - 1929 version.mkv"
|
||||
"/movies/The Phantom of the Opera (1925)/The Phantom of the Opera (1925) - 1929 version.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -81,7 +81,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"/movies/M/Movie 4.mkv",
|
||||
"/movies/M/Movie 5.mkv",
|
||||
"/movies/M/Movie 6.mkv",
|
||||
"/movies/M/Movie 7.mkv"
|
||||
"/movies/M/Movie 7.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -104,7 +104,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"/movies/Movie/Movie-5.mkv",
|
||||
"/movies/Movie/Movie-6.mkv",
|
||||
"/movies/Movie/Movie-7.mkv",
|
||||
"/movies/Movie/Movie-8.mkv"
|
||||
"/movies/Movie/Movie-8.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -128,7 +128,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"/movies/Mo/Movie 6.mkv",
|
||||
"/movies/Mo/Movie 7.mkv",
|
||||
"/movies/Mo/Movie 8.mkv",
|
||||
"/movies/Mo/Movie 9.mkv"
|
||||
"/movies/Mo/Movie 9.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -148,7 +148,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"/movies/Movie/Movie 2.mkv",
|
||||
"/movies/Movie/Movie 3.mkv",
|
||||
"/movies/Movie/Movie 4.mkv",
|
||||
"/movies/Movie/Movie 5.mkv"
|
||||
"/movies/Movie/Movie 5.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -170,7 +170,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"/movies/Iron Man/Iron Man (2008).mkv",
|
||||
"/movies/Iron Man/Iron Man (2009).mkv",
|
||||
"/movies/Iron Man/Iron Man (2010).mkv",
|
||||
"/movies/Iron Man/Iron Man (2011).mkv"
|
||||
"/movies/Iron Man/Iron Man (2011).mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -192,7 +192,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"/movies/Iron Man/Iron Man-bluray.mkv",
|
||||
"/movies/Iron Man/Iron Man-3d.mkv",
|
||||
"/movies/Iron Man/Iron Man-3d-hsbs.mkv",
|
||||
"/movies/Iron Man/Iron Man[test].mkv"
|
||||
"/movies/Iron Man/Iron Man[test].mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -221,7 +221,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"/movies/Iron Man/Iron Man - bluray.mkv",
|
||||
"/movies/Iron Man/Iron Man - 3d.mkv",
|
||||
"/movies/Iron Man/Iron Man - 3d-hsbs.mkv",
|
||||
"/movies/Iron Man/Iron Man [test].mkv"
|
||||
"/movies/Iron Man/Iron Man [test].mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -245,7 +245,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"/movies/Iron Man/Iron Man - B (2006).mkv",
|
||||
"/movies/Iron Man/Iron Man - C (2007).mkv"
|
||||
"/movies/Iron Man/Iron Man - C (2007).mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -266,7 +266,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"/movies/Iron Man/Iron Man_bluray.mkv",
|
||||
"/movies/Iron Man/Iron Man_3d.mkv",
|
||||
"/movies/Iron Man/Iron Man_3d-hsbs.mkv",
|
||||
"/movies/Iron Man/Iron Man_3d.hsbs.mkv"
|
||||
"/movies/Iron Man/Iron Man_3d.hsbs.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -288,7 +288,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"/movies/Iron Man/Iron Man (2008).mkv",
|
||||
"/movies/Iron Man/Iron Man (2009).mkv",
|
||||
"/movies/Iron Man/Iron Man (2010).mkv",
|
||||
"/movies/Iron Man/Iron Man (2011).mkv"
|
||||
"/movies/Iron Man/Iron Man (2011).mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -305,7 +305,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"/movies/Blade Runner (1982)/Blade Runner (1982) [Final Cut] [1080p HEVC AAC].mkv",
|
||||
"/movies/Blade Runner (1982)/Blade Runner (1982) [EE by ADM] [480p HEVC AAC,AAC,AAC].mkv"
|
||||
"/movies/Blade Runner (1982)/Blade Runner (1982) [EE by ADM] [480p HEVC AAC,AAC,AAC].mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -322,7 +322,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"/movies/X-Men Apocalypse (2016)/X-Men Apocalypse (2016) [1080p] Blu-ray.x264.DTS.mkv",
|
||||
"/movies/X-Men Apocalypse (2016)/X-Men Apocalypse (2016) [2160p] Blu-ray.x265.AAC.mkv"
|
||||
"/movies/X-Men Apocalypse (2016)/X-Men Apocalypse (2016) [2160p] Blu-ray.x265.AAC.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -405,7 +405,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"/movies/John Wick - Kapitel 3 (2019) [imdbid=tt6146586]/John Wick - Kapitel 3 (2019) [imdbid=tt6146586] - Version 1.mkv",
|
||||
"/movies/John Wick - Kapitel 3 (2019) [imdbid=tt6146586]/John Wick - Kapitel 3 (2019) [imdbid=tt6146586] - Version 2.mkv"
|
||||
"/movies/John Wick - Kapitel 3 (2019) [imdbid=tt6146586]/John Wick - Kapitel 3 (2019) [imdbid=tt6146586] - Version 2.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -422,7 +422,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"/movies/John Wick - Chapter 3 (2019)/John Wick - Chapter 3 (2019) [Version 1].mkv",
|
||||
"/movies/John Wick - Chapter 3 (2019)/John Wick - Chapter 3 (2019) [Version 2.mkv"
|
||||
"/movies/John Wick - Chapter 3 (2019)/John Wick - Chapter 3 (2019) [Version 2.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -438,6 +438,6 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var result = VideoListResolver.Resolve(new List<VideoFileInfo>(), _namingOptions).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"Bad Boys (2006) part2.mkv",
|
||||
"Bad Boys (2006) part3.mkv",
|
||||
"Bad Boys (2006) part4.mkv",
|
||||
"Bad Boys (2006)-trailer.mkv"
|
||||
"Bad Boys (2006)-trailer.mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -38,7 +38,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"Bad Boys (2006).mkv",
|
||||
"Bad Boys (2007).mkv"
|
||||
"Bad Boys (2007).mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -52,7 +52,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"Bad Boys 2006.mkv",
|
||||
"Bad Boys 2007.mkv"
|
||||
"Bad Boys 2007.mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -66,7 +66,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"300 (2006).mkv",
|
||||
"300 (2007).mkv"
|
||||
"300 (2007).mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -80,7 +80,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"300 2006.mkv",
|
||||
"300 2007.mkv"
|
||||
"300 2007.mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -94,7 +94,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"Star Trek 1 - The motion picture.mkv",
|
||||
"Star Trek 2- The wrath of khan.mkv"
|
||||
"Star Trek 2- The wrath of khan.mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -108,7 +108,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
{
|
||||
"Red Riding in the Year of Our Lord 1983 (2009).mkv",
|
||||
"Red Riding in the Year of Our Lord 1980 (2009).mkv",
|
||||
"Red Riding in the Year of Our Lord 1974 (2009).mkv"
|
||||
"Red Riding in the Year of Our Lord 1974 (2009).mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -122,7 +122,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"d:/movies/300 2006 part1.mkv",
|
||||
"d:/movies/300 2006 part2.mkv"
|
||||
"d:/movies/300 2006 part2.mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -140,7 +140,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"Bad Boys (2006).part2.stv.unrated.multi.1080p.bluray.x264-rough.mkv",
|
||||
"Bad Boys (2006).part3.stv.unrated.multi.1080p.bluray.x264-rough.mkv",
|
||||
"Bad Boys (2006).part4.stv.unrated.multi.1080p.bluray.x264-rough.mkv",
|
||||
"Bad Boys (2006)-trailer.mkv"
|
||||
"Bad Boys (2006)-trailer.mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -157,7 +157,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"Bad Boys (2006) 1.mkv",
|
||||
"Bad Boys (2006) 2.mkv",
|
||||
"Bad Boys (2006) 3.mkv",
|
||||
"Bad Boys (2006)-trailer.mkv"
|
||||
"Bad Boys (2006)-trailer.mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -174,7 +174,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"300 (2006) part2.mkv",
|
||||
"300 (2006) part3.mkv",
|
||||
"300 (2006) part4.mkv",
|
||||
"300 (2006)-trailer.mkv"
|
||||
"300 (2006)-trailer.mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -192,7 +192,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"Bad Boys (2006) part2.mkv",
|
||||
"Bad Boys (2006) part3.mkv",
|
||||
"Bad Boys (2006) parta.mkv",
|
||||
"Bad Boys (2006)-trailer.mkv"
|
||||
"Bad Boys (2006)-trailer.mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -214,7 +214,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"300 (2006) part1.mkv",
|
||||
"300 (2006) part2.mkv",
|
||||
"300 (2006) part3.mkv",
|
||||
"300 (2006)-trailer.mkv"
|
||||
"300 (2006)-trailer.mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -230,7 +230,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"blah blah - cd 1",
|
||||
"blah blah - cd 2"
|
||||
"blah blah - cd 2",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveDirectories(files, _namingOptions).ToList();
|
||||
@@ -247,7 +247,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"300a.mkv",
|
||||
"300b.mkv",
|
||||
"300c.mkv",
|
||||
"300-trailer.mkv"
|
||||
"300-trailer.mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -266,7 +266,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"Avatar",
|
||||
"Avengers part1.mkv",
|
||||
"Avengers part2.mkv",
|
||||
"Avengers part3.mkv"
|
||||
"Avengers part3.mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -295,7 +295,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"300a.mkv",
|
||||
"300b.mkv",
|
||||
"300c.mkv",
|
||||
"300-trailer.mkv"
|
||||
"300-trailer.mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -319,7 +319,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"300 (2006) parte.mkv",
|
||||
"300 (2006) partf.mkv",
|
||||
"300 (2006) partg.mkv",
|
||||
"300 (2006)-trailer.mkv"
|
||||
"300 (2006)-trailer.mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -338,7 +338,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
new FileSystemMetadata { FullName = "Bad Boys (2006) part2.mkv", IsDirectory = false },
|
||||
new FileSystemMetadata { FullName = "300 (2006) part2", IsDirectory = true },
|
||||
new FileSystemMetadata { FullName = "300 (2006) part3", IsDirectory = true },
|
||||
new FileSystemMetadata { FullName = "300 (2006) part1", IsDirectory = true }
|
||||
new FileSystemMetadata { FullName = "300 (2006) part1", IsDirectory = true },
|
||||
};
|
||||
|
||||
var result = StackResolver.Resolve(files, _namingOptions).ToList();
|
||||
@@ -358,7 +358,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"Harry Potter and the Deathly Hallows 1.mkv",
|
||||
"Harry Potter and the Deathly Hallows 2.mkv",
|
||||
"Harry Potter and the Deathly Hallows 3.mkv",
|
||||
"Harry Potter and the Deathly Hallows 4.mkv"
|
||||
"Harry Potter and the Deathly Hallows 4.mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -373,7 +373,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"Neverland (2011)[720p][PG][Voted 6.5][Family-Fantasy]part1.mkv",
|
||||
"Neverland (2011)[720p][PG][Voted 6.5][Family-Fantasy]part2.mkv"
|
||||
"Neverland (2011)[720p][PG][Voted 6.5][Family-Fantasy]part2.mkv",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
|
||||
@@ -389,7 +389,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"M:/Movies (DVD)/Movies (Musical)/The Sound of Music/The Sound of Music (1965) (Disc 01)",
|
||||
"M:/Movies (DVD)/Movies (Musical)/The Sound of Music/The Sound of Music (1965) (Disc 02)"
|
||||
"M:/Movies (DVD)/Movies (Musical)/The Sound of Music/The Sound of Music (1965) (Disc 02)",
|
||||
};
|
||||
|
||||
var result = StackResolver.ResolveDirectories(files, _namingOptions).ToList();
|
||||
@@ -402,6 +402,6 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
{
|
||||
Assert.Equal(fileCount, stack.Files.Count);
|
||||
Assert.Equal(name, stack.Name);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
else
|
||||
{
|
||||
Assert.Null(stubTypeResult);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"trailer.mkv",
|
||||
|
||||
// Same as above
|
||||
"WillyWonka-trailer.mkv"
|
||||
"WillyWonka-trailer.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -75,7 +75,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"300.mkv",
|
||||
"300.nfo"
|
||||
"300.nfo",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -91,7 +91,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"300.mkv",
|
||||
"300 - trailer.mkv"
|
||||
"300 - trailer.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -109,7 +109,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"X-Men Days of Future Past - 1080p.mkv",
|
||||
"X-Men Days of Future Past-trailer.mp4"
|
||||
"X-Men Days of Future Past-trailer.mp4",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -128,7 +128,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
{
|
||||
"X-Men Days of Future Past - 1080p.mkv",
|
||||
"X-Men Days of Future Past-trailer.mp4",
|
||||
"X-Men Days of Future Past-trailer2.mp4"
|
||||
"X-Men Days of Future Past-trailer2.mp4",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -148,7 +148,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
{
|
||||
"Looper (2012)-trailer.mkv",
|
||||
"Looper 2012-trailer.mkv",
|
||||
"Looper.2012.bluray.720p.x264.mkv"
|
||||
"Looper.2012.bluray.720p.x264.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -167,7 +167,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"/movies/Looper (2012)/Looper (2012)-trailer.mkv",
|
||||
"/movies/Looper (2012)/Looper.bluray.720p.x264.mkv"
|
||||
"/movies/Looper (2012)/Looper.bluray.720p.x264.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -189,7 +189,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"My video 2.mkv",
|
||||
"My video 3.mkv",
|
||||
"My video 4.mkv",
|
||||
"My video 5.mkv"
|
||||
"My video 5.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -205,7 +205,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"M:/Movies (DVD)/Movies (Musical)/Sound of Music (1965)/Sound of Music Disc 1",
|
||||
"M:/Movies (DVD)/Movies (Musical)/Sound of Music (1965)/Sound of Music Disc 2"
|
||||
"M:/Movies (DVD)/Movies (Musical)/Sound of Music (1965)/Sound of Music Disc 2",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -222,7 +222,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"My movie #1.mp4",
|
||||
"My movie #2.mp4"
|
||||
"My movie #2.mp4",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -240,7 +240,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"No (2012) part1.mp4",
|
||||
"No (2012) part2.mp4",
|
||||
"No (2012) part1-trailer.mp4",
|
||||
"No (2012)-trailer.mp4"
|
||||
"No (2012)-trailer.mp4",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -261,7 +261,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"/Movies/Top Gun (1984)/movie.mp4",
|
||||
"/Movies/Top Gun (1984)/Top Gun (1984)-trailer.mp4",
|
||||
"/Movies/Top Gun (1984)/Top Gun (1984)-trailer2.mp4",
|
||||
"/Movies/trailer.mp4"
|
||||
"/Movies/trailer.mp4",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -283,7 +283,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
"/MCFAMILY-PC/Private3$/Heterosexual/Breast In Class 2 Counterfeit Racks (2011)/Breast In Class 2 Counterfeit Racks (2011) Disc 1 cd1.avi",
|
||||
"/MCFAMILY-PC/Private3$/Heterosexual/Breast In Class 2 Counterfeit Racks (2011)/Breast In Class 2 Counterfeit Racks (2011) Disc 1 cd2.avi",
|
||||
"/MCFAMILY-PC/Private3$/Heterosexual/Breast In Class 2 Counterfeit Racks (2011)/Breast In Class 2 Disc 2 cd1.avi",
|
||||
"/MCFAMILY-PC/Private3$/Heterosexual/Breast In Class 2 Counterfeit Racks (2011)/Breast In Class 2 Disc 2 cd2.avi"
|
||||
"/MCFAMILY-PC/Private3$/Heterosexual/Breast In Class 2 Counterfeit Racks (2011)/Breast In Class 2 Disc 2 cd2.avi",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -298,7 +298,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
"/nas-markrobbo78/Videos/INDEX HTPC/Movies/Watched/3 - ACTION/Argo (2012)/movie.mkv"
|
||||
"/nas-markrobbo78/Videos/INDEX HTPC/Movies/Watched/3 - ACTION/Argo (2012)/movie.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -313,7 +313,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
"The Colony.mkv"
|
||||
"The Colony.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -329,7 +329,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"Four Sisters and a Wedding - A.avi",
|
||||
"Four Sisters and a Wedding - B.avi"
|
||||
"Four Sisters and a Wedding - B.avi",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -347,7 +347,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"Four Rooms - A.avi",
|
||||
"Four Rooms - A.mp4"
|
||||
"Four Rooms - A.mp4",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -363,7 +363,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"/Server/Despicable Me/Despicable Me (2010).mkv",
|
||||
"/Server/Despicable Me/trailer.mkv"
|
||||
"/Server/Despicable Me/trailer.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -381,7 +381,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"/Server/Despicable Me/Despicable Me (2010).mkv",
|
||||
"/Server/Despicable Me/trailers/some title.mkv"
|
||||
"/Server/Despicable Me/trailers/some title.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -399,7 +399,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
var files = new[]
|
||||
{
|
||||
"/Movies/Despicable Me/Despicable Me.mkv",
|
||||
"/Movies/Despicable Me/trailers/trailer.mkv"
|
||||
"/Movies/Despicable Me/trailers/trailer.mkv",
|
||||
};
|
||||
|
||||
var result = VideoListResolver.Resolve(
|
||||
@@ -416,6 +416,6 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
{
|
||||
var stack = new FileStack(string.Empty, false, Array.Empty<string>());
|
||||
Assert.False(stack.ContainsFile("XX", true));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,7 +192,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
foreach (var result in results)
|
||||
{
|
||||
Assert.Null(result?.Container);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")]
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")]
|
||||
@@ -20,4 +20,3 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
// Generated by the MSBuild WriteCodeFragment class.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "aetk2kDndoI=",
|
||||
"dgSpecHash": "w013NueXjWs=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\tests\\Jellyfin.Naming.Tests\\Jellyfin.Naming.Tests.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "VqeAp96kZ+8=",
|
||||
"dgSpecHash": "dtvsyCwkixc=",
|
||||
"success": true,
|
||||
"projectFilePath": "E:\\Projects\\pgsql-jellyfin\\tests\\Jellyfin.Networking.Tests\\Jellyfin.Networking.Tests.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user