Merge branch 'master' into allocationz

pull/5938/head
Claus Vium 3 years ago committed by GitHub
commit b9d18f0fa7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -502,7 +502,7 @@ namespace Emby.Server.Implementations.Data
using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id")) using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id"))
{ {
saveImagesStatement.TryBind("@Id", item.Id.ToByteArray()); saveImagesStatement.TryBind("@Id", item.Id.ToByteArray());
saveImagesStatement.TryBind("@Images", SerializeImages(item)); saveImagesStatement.TryBind("@Images", SerializeImages(item.ImageInfos));
saveImagesStatement.MoveNext(); saveImagesStatement.MoveNext();
} }
@ -897,8 +897,8 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.TryBind("@ExternalSeriesId", item.ExternalSeriesId); saveItemStatement.TryBind("@ExternalSeriesId", item.ExternalSeriesId);
saveItemStatement.TryBind("@Tagline", item.Tagline); saveItemStatement.TryBind("@Tagline", item.Tagline);
saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item)); saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item.ProviderIds));
saveItemStatement.TryBind("@Images", SerializeImages(item)); saveItemStatement.TryBind("@Images", SerializeImages(item.ImageInfos));
if (item.ProductionLocations.Length > 0) if (item.ProductionLocations.Length > 0)
{ {
@ -968,10 +968,10 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.MoveNext(); saveItemStatement.MoveNext();
} }
private static string SerializeProviderIds(BaseItem item) internal static string SerializeProviderIds(Dictionary<string, string> providerIds)
{ {
StringBuilder str = new StringBuilder(); StringBuilder str = new StringBuilder();
foreach (var i in item.ProviderIds) foreach (var i in providerIds)
{ {
// Ideally we shouldn't need this IsNullOrWhiteSpace check, // Ideally we shouldn't need this IsNullOrWhiteSpace check,
// but we're seeing some cases of bad data slip through // but we're seeing some cases of bad data slip through
@ -995,18 +995,13 @@ namespace Emby.Server.Implementations.Data
return str.ToString(); return str.ToString();
} }
private static void DeserializeProviderIds(string value, BaseItem item) internal static void DeserializeProviderIds(string value, IHasProviderIds item)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
return; return;
} }
if (item.ProviderIds.Count > 0)
{
return;
}
foreach (var part in value.SpanSplit('|')) foreach (var part in value.SpanSplit('|'))
{ {
var providerDelimiterIndex = part.IndexOf('='); var providerDelimiterIndex = part.IndexOf('=');
@ -1017,10 +1012,8 @@ namespace Emby.Server.Implementations.Data
} }
} }
private string SerializeImages(BaseItem item) internal string SerializeImages(ItemImageInfo[] images)
{ {
var images = item.ImageInfos;
if (images.Length == 0) if (images.Length == 0)
{ {
return null; return null;
@ -1042,16 +1035,11 @@ namespace Emby.Server.Implementations.Data
return str.ToString(); return str.ToString();
} }
private void DeserializeImages(string value, BaseItem item) internal ItemImageInfo[] DeserializeImages(string value)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
return; return Array.Empty<ItemImageInfo>();
}
if (item.ImageInfos.Length > 0)
{
return;
} }
var list = new List<ItemImageInfo>(); var list = new List<ItemImageInfo>();
@ -1065,15 +1053,14 @@ namespace Emby.Server.Implementations.Data
} }
} }
item.ImageInfos = list.ToArray(); return list.ToArray();
} }
public void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image) private void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image)
{ {
const char Delimiter = '*'; const char Delimiter = '*';
var path = image.Path ?? string.Empty; var path = image.Path ?? string.Empty;
var hash = image.BlurHash ?? string.Empty;
bldr.Append(GetPathToSave(path)) bldr.Append(GetPathToSave(path))
.Append(Delimiter) .Append(Delimiter)
@ -1083,11 +1070,16 @@ namespace Emby.Server.Implementations.Data
.Append(Delimiter) .Append(Delimiter)
.Append(image.Width) .Append(image.Width)
.Append(Delimiter) .Append(Delimiter)
.Append(image.Height) .Append(image.Height);
.Append(Delimiter)
// Replace delimiters with other characters. var hash = image.BlurHash;
// This can be removed when we migrate to a proper DB. if (!string.IsNullOrEmpty(hash))
.Append(hash.Replace('*', '/').Replace('|', '\\')); {
bldr.Append(Delimiter)
// Replace delimiters with other characters.
// This can be removed when we migrate to a proper DB.
.Append(hash.Replace('*', '/').Replace('|', '\\'));
}
} }
private ItemImageInfo ItemImageInfoFromValueString(ReadOnlySpan<char> value) private ItemImageInfo ItemImageInfoFromValueString(ReadOnlySpan<char> value)
@ -1834,7 +1826,7 @@ namespace Emby.Server.Implementations.Data
index++; index++;
} }
if (!reader.IsDBNull(index)) if (item.ProviderIds.Count == 0 && !reader.IsDBNull(index))
{ {
DeserializeProviderIds(reader.GetString(index), item); DeserializeProviderIds(reader.GetString(index), item);
} }
@ -1843,9 +1835,9 @@ namespace Emby.Server.Implementations.Data
if (query.DtoOptions.EnableImages) if (query.DtoOptions.EnableImages)
{ {
if (!reader.IsDBNull(index)) if (item.ImageInfos.Length == 0 && !reader.IsDBNull(index))
{ {
DeserializeImages(reader.GetString(index), item); item.ImageInfos = DeserializeImages(reader.GetString(index));
} }
index++; index++;

@ -31,7 +31,7 @@
<PackageReference Include="Mono.Nat" Version="3.0.1" /> <PackageReference Include="Mono.Nat" Version="3.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.0.0" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.0.0" />
<PackageReference Include="sharpcompress" Version="0.28.2" /> <PackageReference Include="sharpcompress" Version="0.28.2" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.2.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.2" /> <PackageReference Include="DotNet.Glob" Version="3.1.2" />
</ItemGroup> </ItemGroup>

@ -260,8 +260,6 @@ namespace Emby.Server.Implementations.IO
result.Exists = false; result.Exists = false;
} }
} }
result.DirectoryName = fileInfo.DirectoryName;
} }
result.CreationTimeUtc = GetCreationTimeUtc(info); result.CreationTimeUtc = GetCreationTimeUtc(info);

@ -558,7 +558,6 @@ namespace Emby.Server.Implementations.Library
var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService) var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService)
{ {
Parent = parent, Parent = parent,
Path = fullPath,
FileInfo = fileInfo, FileInfo = fileInfo,
CollectionType = collectionType, CollectionType = collectionType,
LibraryOptions = libraryOptions LibraryOptions = libraryOptions
@ -684,7 +683,7 @@ namespace Emby.Server.Implementations.Library
foreach (var item in items) foreach (var item in items)
{ {
ResolverHelper.SetInitialItemValues(item, parent, _fileSystem, this, directoryService); ResolverHelper.SetInitialItemValues(item, parent, this, directoryService);
} }
items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions)); items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions));

@ -1,3 +1,5 @@
#nullable enable
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -18,11 +20,10 @@ namespace Emby.Server.Implementations.Library
/// </summary> /// </summary>
/// <param name="item">The item.</param> /// <param name="item">The item.</param>
/// <param name="parent">The parent.</param> /// <param name="parent">The parent.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="libraryManager">The library manager.</param> /// <param name="libraryManager">The library manager.</param>
/// <param name="directoryService">The directory service.</param> /// <param name="directoryService">The directory service.</param>
/// <exception cref="ArgumentException">Item must have a path</exception> /// <exception cref="ArgumentException">Item must have a path.</exception>
public static void SetInitialItemValues(BaseItem item, Folder parent, IFileSystem fileSystem, ILibraryManager libraryManager, IDirectoryService directoryService) public static void SetInitialItemValues(BaseItem item, Folder? parent, ILibraryManager libraryManager, IDirectoryService directoryService)
{ {
// This version of the below method has no ItemResolveArgs, so we have to require the path already being set // This version of the below method has no ItemResolveArgs, so we have to require the path already being set
if (string.IsNullOrEmpty(item.Path)) if (string.IsNullOrEmpty(item.Path))
@ -43,9 +44,9 @@ namespace Emby.Server.Implementations.Library
// Make sure DateCreated and DateModified have values // Make sure DateCreated and DateModified have values
var fileInfo = directoryService.GetFile(item.Path); var fileInfo = directoryService.GetFile(item.Path);
SetDateCreated(item, fileSystem, fileInfo); SetDateCreated(item, fileInfo);
EnsureName(item, item.Path, fileInfo); EnsureName(item, fileInfo);
} }
/// <summary> /// <summary>
@ -72,9 +73,9 @@ namespace Emby.Server.Implementations.Library
item.Id = libraryManager.GetNewItemId(item.Path, item.GetType()); item.Id = libraryManager.GetNewItemId(item.Path, item.GetType());
// Make sure the item has a name // Make sure the item has a name
EnsureName(item, item.Path, args.FileInfo); EnsureName(item, args.FileInfo);
item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 || item.IsLocked = item.Path.Contains("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) ||
item.GetParents().Any(i => i.IsLocked); item.GetParents().Any(i => i.IsLocked);
// Make sure DateCreated and DateModified have values // Make sure DateCreated and DateModified have values
@ -84,28 +85,15 @@ namespace Emby.Server.Implementations.Library
/// <summary> /// <summary>
/// Ensures the name. /// Ensures the name.
/// </summary> /// </summary>
private static void EnsureName(BaseItem item, string fullPath, FileSystemMetadata fileInfo) private static void EnsureName(BaseItem item, FileSystemMetadata fileInfo)
{ {
// If the subclass didn't supply a name, add it here // If the subclass didn't supply a name, add it here
if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(fullPath)) if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(item.Path))
{ {
var fileName = fileInfo == null ? Path.GetFileName(fullPath) : fileInfo.Name; item.Name = fileInfo.IsDirectory ? fileInfo.Name : Path.GetFileNameWithoutExtension(fileInfo.Name);
item.Name = GetDisplayName(fileName, fileInfo != null && fileInfo.IsDirectory);
} }
} }
/// <summary>
/// Gets the display name.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="isDirectory">if set to <c>true</c> [is directory].</param>
/// <returns>System.String.</returns>
private static string GetDisplayName(string path, bool isDirectory)
{
return isDirectory ? Path.GetFileName(path) : Path.GetFileNameWithoutExtension(path);
}
/// <summary> /// <summary>
/// Ensures DateCreated and DateModified have values. /// Ensures DateCreated and DateModified have values.
/// </summary> /// </summary>
@ -114,21 +102,6 @@ namespace Emby.Server.Implementations.Library
/// <param name="args">The args.</param> /// <param name="args">The args.</param>
private static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args) private static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args)
{ {
if (fileSystem == null)
{
throw new ArgumentNullException(nameof(fileSystem));
}
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
if (args == null)
{
throw new ArgumentNullException(nameof(args));
}
// See if a different path came out of the resolver than what went in // See if a different path came out of the resolver than what went in
if (!fileSystem.AreEqual(args.Path, item.Path)) if (!fileSystem.AreEqual(args.Path, item.Path))
{ {
@ -136,7 +109,7 @@ namespace Emby.Server.Implementations.Library
if (childData != null) if (childData != null)
{ {
SetDateCreated(item, fileSystem, childData); SetDateCreated(item, childData);
} }
else else
{ {
@ -144,17 +117,17 @@ namespace Emby.Server.Implementations.Library
if (fileData.Exists) if (fileData.Exists)
{ {
SetDateCreated(item, fileSystem, fileData); SetDateCreated(item, fileData);
} }
} }
} }
else else
{ {
SetDateCreated(item, fileSystem, args.FileInfo); SetDateCreated(item, args.FileInfo);
} }
} }
private static void SetDateCreated(BaseItem item, IFileSystem fileSystem, FileSystemMetadata info) private static void SetDateCreated(BaseItem item, FileSystemMetadata? info)
{ {
var config = BaseItem.ConfigurationManager.GetMetadataConfiguration(); var config = BaseItem.ConfigurationManager.GetMetadataConfiguration();
@ -163,7 +136,7 @@ namespace Emby.Server.Implementations.Library
// directoryService.getFile may return null // directoryService.getFile may return null
if (info != null) if (info != null)
{ {
var dateCreated = fileSystem.GetCreationTimeUtc(info); var dateCreated = info.CreationTimeUtc;
if (dateCreated.Equals(DateTime.MinValue)) if (dateCreated.Equals(DateTime.MinValue))
{ {

@ -166,9 +166,7 @@ namespace Emby.Server.Implementations.Plugins
/// </summary> /// </summary>
public void CreatePlugins() public void CreatePlugins()
{ {
_ = _appHost.GetExports<IPlugin>(CreatePluginInstance) _ = _appHost.GetExports<IPlugin>(CreatePluginInstance);
.Where(i => i != null)
.ToArray();
} }
/// <summary> /// <summary>

@ -237,48 +237,6 @@ namespace Jellyfin.Api.Controllers
return Ok(results); return Ok(results);
} }
/// <summary>
/// Gets a remote image.
/// </summary>
/// <param name="imageUrl">The image url.</param>
/// <param name="providerName">The provider name.</param>
/// <response code="200">Remote image retrieved.</response>
/// <returns>
/// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
/// The task result contains an <see cref="FileStreamResult"/> containing the images file stream.
/// </returns>
[HttpGet("Items/RemoteSearch/Image")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesImageFile]
public async Task<ActionResult> GetRemoteSearchImage(
[FromQuery, Required] string imageUrl,
[FromQuery, Required] string providerName)
{
var urlHash = imageUrl.GetMD5();
var pointerCachePath = GetFullCachePath(urlHash.ToString());
try
{
var contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false);
if (System.IO.File.Exists(contentPath))
{
return PhysicalFile(contentPath, MimeTypes.GetMimeType(contentPath));
}
}
catch (FileNotFoundException)
{
// Means the file isn't cached yet
}
catch (IOException)
{
// Means the file isn't cached yet
}
await DownloadImage(providerName, imageUrl, urlHash, pointerCachePath).ConfigureAwait(false);
var updatedContentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false);
return PhysicalFile(updatedContentPath, MimeTypes.GetMimeType(updatedContentPath));
}
/// <summary> /// <summary>
/// Applies search criteria to an item and refreshes metadata. /// Applies search criteria to an item and refreshes metadata.
/// </summary> /// </summary>
@ -320,54 +278,5 @@ namespace Jellyfin.Api.Controllers
return NoContent(); return NoContent();
} }
/// <summary>
/// Downloads the image.
/// </summary>
/// <param name="providerName">Name of the provider.</param>
/// <param name="url">The URL.</param>
/// <param name="urlHash">The URL hash.</param>
/// <param name="pointerCachePath">The pointer cache path.</param>
/// <returns>Task.</returns>
private async Task DownloadImage(string providerName, string url, Guid urlHash, string pointerCachePath)
{
using var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false);
if (result.Content.Headers.ContentType?.MediaType == null)
{
throw new ResourceNotFoundException(nameof(result.Content.Headers.ContentType));
}
var ext = result.Content.Headers.ContentType.MediaType.Split('/')[^1];
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
var directory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid.");
Directory.CreateDirectory(directory);
using (var stream = result.Content)
{
// use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
await using var fileStream = new FileStream(
fullCachePath,
FileMode.Create,
FileAccess.Write,
FileShare.None,
IODefaults.FileStreamBufferSize,
true);
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
}
var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath));
Directory.CreateDirectory(pointerCacheDirectory);
await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath).ConfigureAwait(false);
}
/// <summary>
/// Gets the full cache path.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>System.String.</returns>
private string GetFullCachePath(string filename)
=> Path.Combine(_appPaths.CachePath, "remote-images", filename.Substring(0, 1), filename);
} }
} }

@ -145,58 +145,6 @@ namespace Jellyfin.Api.Controllers
return Ok(_providerManager.GetRemoteImageProviderInfo(item)); return Ok(_providerManager.GetRemoteImageProviderInfo(item));
} }
/// <summary>
/// Gets a remote image.
/// </summary>
/// <param name="imageUrl">The image url.</param>
/// <response code="200">Remote image returned.</response>
/// <response code="404">Remote image not found.</response>
/// <returns>Image Stream.</returns>
[HttpGet("Images/Remote")]
[Produces(MediaTypeNames.Application.Octet)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesImageFile]
public async Task<ActionResult> GetRemoteImage([FromQuery, Required] Uri imageUrl)
{
var urlHash = imageUrl.ToString().GetMD5();
var pointerCachePath = GetFullCachePath(urlHash.ToString());
string? contentPath = null;
var hasFile = false;
try
{
contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false);
if (System.IO.File.Exists(contentPath))
{
hasFile = true;
}
}
catch (FileNotFoundException)
{
// The file isn't cached yet
}
catch (IOException)
{
// The file isn't cached yet
}
if (!hasFile)
{
await DownloadImage(imageUrl, urlHash, pointerCachePath).ConfigureAwait(false);
contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false);
}
if (string.IsNullOrEmpty(contentPath))
{
return NotFound();
}
var contentType = MimeTypes.GetMimeType(contentPath);
return PhysicalFile(contentPath, contentType);
}
/// <summary> /// <summary>
/// Downloads a remote image for an item. /// Downloads a remote image for an item.
/// </summary> /// </summary>

@ -527,7 +527,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param> /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
/// <param name="playSessionId">The play session id.</param> /// <param name="playSessionId">The play session id.</param>
/// <param name="segmentContainer">The segment container.</param> /// <param name="segmentContainer">The segment container.</param>
/// <param name="segmentLength">The segment lenght.</param> /// <param name="segmentLength">The segment length.</param>
/// <param name="minSegments">The minimum number of segments.</param> /// <param name="minSegments">The minimum number of segments.</param>
/// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
/// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
@ -556,7 +556,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param> /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
/// <param name="requireAvc">Optional. Whether to require avc.</param> /// <param name="requireAvc">Optional. Whether to require avc.</param>
/// <param name="deInterlace">Optional. Whether to deinterlace the video.</param> /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
/// <param name="requireNonAnamorphic">Optional. Whether to require a non anamporphic stream.</param> /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
/// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param> /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
/// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
/// <param name="liveStreamId">The live stream id.</param> /// <param name="liveStreamId">The live stream id.</param>
@ -570,8 +570,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="streamOptions">Optional. The streaming options.</param> /// <param name="streamOptions">Optional. The streaming options.</param>
/// <response code="200">Video stream returned.</response> /// <response code="200">Video stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns> /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
[HttpGet("{itemId}/{stream=stream}.{container}")] [HttpGet("{itemId}/stream.{container}")]
[HttpHead("{itemId}/{stream=stream}.{container}", Name = "HeadVideoStreamByContainer")] [HttpHead("{itemId}/stream.{container}", Name = "HeadVideoStreamByContainer")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesVideoFile] [ProducesVideoFile]
public Task<ActionResult> GetVideoStreamByContainer( public Task<ActionResult> GetVideoStreamByContainer(

@ -120,8 +120,7 @@ namespace MediaBrowser.Controller.Entities
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService) var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService)
{ {
FileInfo = FileSystem.GetDirectoryInfo(path), FileInfo = FileSystem.GetDirectoryInfo(path)
Path = path
}; };
// Gather child folder and files // Gather child folder and files

@ -271,7 +271,6 @@ namespace MediaBrowser.Controller.Entities
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService) var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService)
{ {
FileInfo = FileSystem.GetDirectoryInfo(path), FileInfo = FileSystem.GetDirectoryInfo(path),
Path = path,
Parent = GetParent() as Folder, Parent = GetParent() as Folder,
CollectionType = CollectionType CollectionType = CollectionType
}; };

@ -60,10 +60,10 @@ namespace MediaBrowser.Controller.Library
public FileSystemMetadata FileInfo { get; set; } public FileSystemMetadata FileInfo { get; set; }
/// <summary> /// <summary>
/// Gets or sets the path. /// Gets the path.
/// </summary> /// </summary>
/// <value>The path.</value> /// <value>The path.</value>
public string Path { get; set; } public string Path => FileInfo.FullName;
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is directory. /// Gets a value indicating whether this instance is directory.

@ -37,12 +37,6 @@ namespace MediaBrowser.Model.IO
/// <value>The length.</value> /// <value>The length.</value>
public long Length { get; set; } public long Length { get; set; }
/// <summary>
/// Gets or sets the name of the directory.
/// </summary>
/// <value>The name of the directory.</value>
public string DirectoryName { get; set; }
/// <summary> /// <summary>
/// Gets or sets the last write time UTC. /// Gets or sets the last write time UTC.
/// </summary> /// </summary>

@ -63,19 +63,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <returns>The Jellyfin person type.</returns> /// <returns>The Jellyfin person type.</returns>
public static string MapCrewToPersonType(Crew crew) public static string MapCrewToPersonType(Crew crew)
{ {
if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase) if (crew.Department.Equals("production", StringComparison.OrdinalIgnoreCase)
&& crew.Job.Contains("director", StringComparison.InvariantCultureIgnoreCase)) && crew.Job.Contains("director", StringComparison.OrdinalIgnoreCase))
{ {
return PersonType.Director; return PersonType.Director;
} }
if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase) if (crew.Department.Equals("production", StringComparison.OrdinalIgnoreCase)
&& crew.Job.Contains("producer", StringComparison.InvariantCultureIgnoreCase)) && crew.Job.Contains("producer", StringComparison.OrdinalIgnoreCase))
{ {
return PersonType.Producer; return PersonType.Producer;
} }
if (crew.Department.Equals("writing", StringComparison.InvariantCultureIgnoreCase)) if (crew.Department.Equals("writing", StringComparison.OrdinalIgnoreCase))
{ {
return PersonType.Writer; return PersonType.Writer;
} }

@ -16,8 +16,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.16.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.16.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.5" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />

@ -0,0 +1,229 @@
using System;
using System.Collections.Generic;
using AutoFixture;
using AutoFixture.AutoMoq;
using Emby.Server.Implementations.Data;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Entities;
using Moq;
using Xunit;
namespace Jellyfin.Server.Implementations.Tests.Data
{
public class SqliteItemRepositoryTests
{
public const string VirtualMetaDataPath = "%MetadataPath%";
public const string MetaDataPath = "/meta/data/path";
private readonly IFixture _fixture;
private readonly SqliteItemRepository _sqliteItemRepository;
public SqliteItemRepositoryTests()
{
var appHost = new Mock<IServerApplicationHost>();
appHost.Setup(x => x.ExpandVirtualPath(It.IsAny<string>()))
.Returns((string x) => x.Replace(VirtualMetaDataPath, MetaDataPath, StringComparison.Ordinal));
appHost.Setup(x => x.ReverseVirtualPath(It.IsAny<string>()))
.Returns((string x) => x.Replace(MetaDataPath, VirtualMetaDataPath, StringComparison.Ordinal));
_fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });
_fixture.Inject(appHost);
_sqliteItemRepository = _fixture.Create<SqliteItemRepository>();
}
public static IEnumerable<object[]> ItemImageInfoFromValueString_Valid_TestData()
{
yield return new object[]
{
"/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN",
new ItemImageInfo()
{
Path = "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg",
Type = ImageType.Primary,
DateModified = new DateTime(637452096478512963, DateTimeKind.Utc),
Width = 1920,
Height = 1080,
BlurHash = "WjQbtJtSO8nhNZ%L_Io#R*oaS6o}-;adXAoIn7j[%hW9s:WGw[nN"
}
};
yield return new object[]
{
"https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary*0*0",
new ItemImageInfo()
{
Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg",
Type = ImageType.Primary,
}
};
yield return new object[]
{
"%MetadataPath%/library/68/68578562b96c80a7ebd530848801f645/poster.jpg*637264380567586027*Primary*600*336",
new ItemImageInfo()
{
Path = "/meta/data/path/library/68/68578562b96c80a7ebd530848801f645/poster.jpg",
Type = ImageType.Primary,
DateModified = new DateTime(637264380567586027, DateTimeKind.Utc),
Width = 600,
Height = 336
}
};
}
[Theory]
[MemberData(nameof(ItemImageInfoFromValueString_Valid_TestData))]
public void ItemImageInfoFromValueString_Valid_Success(string value, ItemImageInfo expected)
{
var result = _sqliteItemRepository.ItemImageInfoFromValueString(value);
Assert.Equal(expected.Path, result.Path);
Assert.Equal(expected.Type, result.Type);
Assert.Equal(expected.DateModified, result.DateModified);
Assert.Equal(expected.Width, result.Width);
Assert.Equal(expected.Height, result.Height);
Assert.Equal(expected.BlurHash, result.BlurHash);
}
[Theory]
[InlineData("")]
[InlineData("*")]
public void ItemImageInfoFromValueString_Invalid_Null(string value)
{
Assert.Null(_sqliteItemRepository.ItemImageInfoFromValueString(value));
}
public static IEnumerable<object[]> DeserializeImages_Valid_TestData()
{
yield return new object[]
{
"/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN",
new ItemImageInfo[]
{
new ItemImageInfo()
{
Path = "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg",
Type = ImageType.Primary,
DateModified = new DateTime(637452096478512963, DateTimeKind.Utc),
Width = 1920,
Height = 1080,
BlurHash = "WjQbtJtSO8nhNZ%L_Io#R*oaS6o}-;adXAoIn7j[%hW9s:WGw[nN"
}
}
};
yield return new object[]
{
"%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/poster.jpg*637261226720645297*Primary*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/logo.png*637261226720805297*Logo*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/landscape.jpg*637261226721285297*Thumb*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/backdrop.jpg*637261226721685297*Backdrop*0*0",
new ItemImageInfo[]
{
new ItemImageInfo()
{
Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/poster.jpg",
Type = ImageType.Primary,
DateModified = new DateTime(637261226720645297, DateTimeKind.Utc),
},
new ItemImageInfo()
{
Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/logo.png",
Type = ImageType.Logo,
DateModified = new DateTime(637261226720805297, DateTimeKind.Utc),
},
new ItemImageInfo()
{
Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/landscape.jpg",
Type = ImageType.Thumb,
DateModified = new DateTime(637261226721285297, DateTimeKind.Utc),
},
new ItemImageInfo()
{
Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/backdrop.jpg",
Type = ImageType.Backdrop,
DateModified = new DateTime(637261226721685297, DateTimeKind.Utc),
}
}
};
}
[Theory]
[MemberData(nameof(DeserializeImages_Valid_TestData))]
public void DeserializeImages_Valid_Success(string value, ItemImageInfo[] expected)
{
var result = _sqliteItemRepository.DeserializeImages(value);
Assert.Equal(expected.Length, result.Length);
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(expected[i].Path, result[i].Path);
Assert.Equal(expected[i].Type, result[i].Type);
Assert.Equal(expected[i].DateModified, result[i].DateModified);
Assert.Equal(expected[i].Width, result[i].Width);
Assert.Equal(expected[i].Height, result[i].Height);
Assert.Equal(expected[i].BlurHash, result[i].BlurHash);
}
}
[Theory]
[MemberData(nameof(DeserializeImages_Valid_TestData))]
public void SerializeImages_Valid_Success(string expected, ItemImageInfo[] value)
{
Assert.Equal(expected, _sqliteItemRepository.SerializeImages(value));
}
public static IEnumerable<object[]> DeserializeProviderIds_Valid_TestData()
{
yield return new object[]
{
"Imdb=tt0119567",
new Dictionary<string, string>()
{
{ "Imdb", "tt0119567" },
}
};
yield return new object[]
{
"Imdb=tt0119567|Tmdb=330|TmdbCollection=328",
new Dictionary<string, string>()
{
{ "Imdb", "tt0119567" },
{ "Tmdb", "330" },
{ "TmdbCollection", "328" },
}
};
yield return new object[]
{
"MusicBrainzAlbum=9d363e43-f24f-4b39-bc5a-7ef305c677c7|MusicBrainzReleaseGroup=63eba062-847c-3b73-8b0f-6baf27bba6fa|AudioDbArtist=111352|AudioDbAlbum=2116560|MusicBrainzAlbumArtist=20244d07-534f-4eff-b4d4-930878889970",
new Dictionary<string, string>()
{
{ "MusicBrainzAlbum", "9d363e43-f24f-4b39-bc5a-7ef305c677c7" },
{ "MusicBrainzReleaseGroup", "63eba062-847c-3b73-8b0f-6baf27bba6fa" },
{ "AudioDbArtist", "111352" },
{ "AudioDbAlbum", "2116560" },
{ "MusicBrainzAlbumArtist", "20244d07-534f-4eff-b4d4-930878889970" },
}
};
}
[Theory]
[MemberData(nameof(DeserializeProviderIds_Valid_TestData))]
public void DeserializeProviderIds_Valid_Success(string value, Dictionary<string, string> expected)
{
var result = new ProviderIdsExtensionsTestsObject();
SqliteItemRepository.DeserializeProviderIds(value, result);
Assert.Equal(expected, result.ProviderIds);
}
[Theory]
[MemberData(nameof(DeserializeProviderIds_Valid_TestData))]
public void SerializeProviderIds_Valid_Success(string expected, Dictionary<string, string> values)
{
Assert.Equal(expected, SqliteItemRepository.SerializeProviderIds(values));
}
private class ProviderIdsExtensionsTestsObject : IHasProviderIds
{
public Dictionary<string, string> ProviderIds { get; set; } = new Dictionary<string, string>();
}
}
}

@ -23,7 +23,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.16.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="Moq" Version="4.16.1" /> <PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.1" />

@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using Moq; using Moq;
using Xunit; using Xunit;
@ -28,7 +29,10 @@ namespace Jellyfin.Server.Implementations.Tests.Library
{ {
Parent = parent, Parent = parent,
CollectionType = CollectionType.TvShows, CollectionType = CollectionType.TvShows,
Path = "All My Children/Season 01/Extras/All My Children S01E01 - Behind The Scenes.mkv" FileInfo = new FileSystemMetadata()
{
FullName = "All My Children/Season 01/Extras/All My Children S01E01 - Behind The Scenes.mkv"
}
}; };
Assert.Null(episodeResolver.Resolve(itemResolveArgs)); Assert.Null(episodeResolver.Resolve(itemResolveArgs));
@ -48,7 +52,10 @@ namespace Jellyfin.Server.Implementations.Tests.Library
{ {
Parent = series, Parent = series,
CollectionType = CollectionType.TvShows, CollectionType = CollectionType.TvShows,
Path = "Extras/Extras S01E01.mkv" FileInfo = new FileSystemMetadata()
{
FullName = "Extras/Extras S01E01.mkv"
}
}; };
Assert.NotNull(episodeResolver.Resolve(itemResolveArgs)); Assert.NotNull(episodeResolver.Resolve(itemResolveArgs));
} }

@ -10,8 +10,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.16.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.16.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.5" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
@ -22,6 +22,13 @@
<PackageReference Include="Moq" Version="4.16.0" /> <PackageReference Include="Moq" Version="4.16.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<!-- Don't run tests in parallel -->
<None Update="xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<!-- Code Analyzers --> <!-- Code Analyzers -->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />

@ -0,0 +1,4 @@
{
"parallelizeAssembly": false,
"parallelizeTestCollections": false
}

@ -11,8 +11,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.16.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.16.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.5" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />

Loading…
Cancel
Save