Преглед на файлове

Merge branch 'master' into allocationz

Claus Vium преди 4 години
родител
ревизия
b9d18f0fa7
променени са 21 файла, в които са добавени 311 реда и са изтрити 255 реда
  1. 24 32
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  2. 1 1
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  3. 0 2
      Emby.Server.Implementations/IO/ManagedFileSystem.cs
  4. 1 2
      Emby.Server.Implementations/Library/LibraryManager.cs
  5. 16 43
      Emby.Server.Implementations/Library/ResolverHelper.cs
  6. 1 3
      Emby.Server.Implementations/Plugins/PluginManager.cs
  7. 0 91
      Jellyfin.Api/Controllers/ItemLookupController.cs
  8. 0 52
      Jellyfin.Api/Controllers/RemoteImageController.cs
  9. 4 4
      Jellyfin.Api/Controllers/VideosController.cs
  10. 1 2
      MediaBrowser.Controller/Entities/AggregateFolder.cs
  11. 0 1
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  12. 2 2
      MediaBrowser.Controller/Library/ItemResolveArgs.cs
  13. 0 6
      MediaBrowser.Model/IO/FileSystemMetadata.cs
  14. 5 5
      MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
  15. 2 2
      tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
  16. 229 0
      tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs
  17. 1 1
      tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
  18. 9 2
      tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
  19. 9 2
      tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
  20. 4 0
      tests/Jellyfin.Server.Integration.Tests/xunit.runner.json
  21. 2 2
      tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj

+ 24 - 32
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -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("@Images", SerializeImages(item));
+            saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item.ProviderIds));
+            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;
-            }
-
-            if (item.ImageInfos.Length > 0)
-            {
-                return;
+                return Array.Empty<ItemImageInfo>();
             }
             }
 
 
             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(Delimiter)
-                // Replace delimiters with other characters.
-                // This can be removed when we migrate to a proper DB.
-                .Append(hash.Replace('*', '/').Replace('|', '\\'));
+                .Append(image.Height);
+
+            var hash = image.BlurHash;
+            if (!string.IsNullOrEmpty(hash))
+            {
+                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++;

+ 1 - 1
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -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>
 
 

+ 0 - 2
Emby.Server.Implementations/IO/ManagedFileSystem.cs

@@ -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);

+ 1 - 2
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -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));

+ 16 - 43
Emby.Server.Implementations/Library/ResolverHelper.cs

@@ -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>
-        public static void SetInitialItemValues(BaseItem item, Folder parent, IFileSystem fileSystem, ILibraryManager libraryManager, IDirectoryService directoryService)
+        /// <exception cref="ArgumentException">Item must have a path.</exception>
+        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 = GetDisplayName(fileName, fileInfo != null && fileInfo.IsDirectory);
+                item.Name = fileInfo.IsDirectory ? fileInfo.Name : Path.GetFileNameWithoutExtension(fileInfo.Name);
             }
             }
         }
         }
 
 
-        /// <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))
                     {
                     {

+ 1 - 3
Emby.Server.Implementations/Plugins/PluginManager.cs

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

+ 0 - 91
Jellyfin.Api/Controllers/ItemLookupController.cs

@@ -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);
     }
     }
 }
 }

+ 0 - 52
Jellyfin.Api/Controllers/RemoteImageController.cs

@@ -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>

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

@@ -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}")]
-        [HttpHead("{itemId}/{stream=stream}.{container}", Name = "HeadVideoStreamByContainer")]
+        [HttpGet("{itemId}/stream.{container}")]
+        [HttpHead("{itemId}/stream.{container}", Name = "HeadVideoStreamByContainer")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesVideoFile]
         [ProducesVideoFile]
         public Task<ActionResult> GetVideoStreamByContainer(
         public Task<ActionResult> GetVideoStreamByContainer(

+ 1 - 2
MediaBrowser.Controller/Entities/AggregateFolder.cs

@@ -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),
-                Path = path
+                FileInfo = FileSystem.GetDirectoryInfo(path)
             };
             };
 
 
             // Gather child folder and files
             // Gather child folder and files

+ 0 - 1
MediaBrowser.Controller/Entities/CollectionFolder.cs

@@ -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
             };
             };

+ 2 - 2
MediaBrowser.Controller/Library/ItemResolveArgs.cs

@@ -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.

+ 0 - 6
MediaBrowser.Model/IO/FileSystemMetadata.cs

@@ -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>

+ 5 - 5
MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs

@@ -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)
-                && crew.Job.Contains("director", StringComparison.InvariantCultureIgnoreCase))
+            if (crew.Department.Equals("production", StringComparison.OrdinalIgnoreCase)
+                && crew.Job.Contains("director", StringComparison.OrdinalIgnoreCase))
             {
             {
                 return PersonType.Director;
                 return PersonType.Director;
             }
             }
 
 
-            if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase)
-                && crew.Job.Contains("producer", StringComparison.InvariantCultureIgnoreCase))
+            if (crew.Department.Equals("production", StringComparison.OrdinalIgnoreCase)
+                && 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;
             }
             }

+ 2 - 2
tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj

@@ -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.Xunit2" Version="4.16.0" />
+    <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.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" />

+ 229 - 0
tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs

@@ -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>();
+        }
+    }
+}

+ 1 - 1
tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj

@@ -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" />

+ 9 - 2
tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs

@@ -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));
         }
         }

+ 9 - 2
tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj

@@ -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.Xunit2" Version="4.16.0" />
+    <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.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" />

+ 4 - 0
tests/Jellyfin.Server.Integration.Tests/xunit.runner.json

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

+ 2 - 2
tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj

@@ -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.Xunit2" Version="4.16.0" />
+    <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.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" />