Browse Source

Merge pull request #5634 from cvium/directoryservice-case-sensitive

make directoryservice cache case sensitive

(cherry picked from commit 1de031a7c386cf8423c23fc00acb1927c440b934)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
Bond-009 4 years ago
parent
commit
970eaf8dfb

+ 3 - 3
MediaBrowser.Controller/Providers/DirectoryService.cs

@@ -12,11 +12,11 @@ namespace MediaBrowser.Controller.Providers
     {
         private readonly IFileSystem _fileSystem;
 
-        private readonly ConcurrentDictionary<string, FileSystemMetadata[]> _cache = new ConcurrentDictionary<string, FileSystemMetadata[]>(StringComparer.OrdinalIgnoreCase);
+        private readonly ConcurrentDictionary<string, FileSystemMetadata[]> _cache = new (StringComparer.Ordinal);
 
-        private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache = new ConcurrentDictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
+        private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache = new (StringComparer.Ordinal);
 
-        private readonly ConcurrentDictionary<string, List<string>> _filePathCache = new ConcurrentDictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
+        private readonly ConcurrentDictionary<string, List<string>> _filePathCache = new (StringComparer.Ordinal);
 
         public DirectoryService(IFileSystem fileSystem)
         {

+ 200 - 0
tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs

@@ -0,0 +1,200 @@
+using System.Linq;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.IO;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Controller.Tests
+{
+    public class DirectoryServiceTests
+    {
+        private const string LowerCasePath = "/music/someartist";
+        private const string UpperCasePath = "/music/SOMEARTIST";
+
+        private static readonly FileSystemMetadata[] _lowerCaseFileSystemMetadata =
+        {
+            new ()
+            {
+                FullName = LowerCasePath + "/Artwork",
+                IsDirectory = true
+            },
+            new ()
+            {
+                FullName = LowerCasePath + "/Some Other Folder",
+                IsDirectory = true
+            },
+            new ()
+            {
+                FullName = LowerCasePath + "/Song 2.mp3",
+                IsDirectory = false
+            },
+            new ()
+            {
+                FullName = LowerCasePath + "/Song 3.mp3",
+                IsDirectory = false
+            }
+        };
+
+        private static readonly FileSystemMetadata[] _upperCaseFileSystemMetadata =
+        {
+            new ()
+            {
+                FullName = UpperCasePath + "/Lyrics",
+                IsDirectory = true
+            },
+            new ()
+            {
+                FullName = UpperCasePath + "/Song 1.mp3",
+                IsDirectory = false
+            }
+        };
+
+        [Fact]
+        public void GetFileSystemEntries_GivenPathsWithDifferentCasing_CachesAll()
+        {
+            var fileSystemMock = new Mock<IFileSystem>();
+            fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(_upperCaseFileSystemMetadata);
+            fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(_lowerCaseFileSystemMetadata);
+            var directoryService = new DirectoryService(fileSystemMock.Object);
+
+            var upperCaseResult = directoryService.GetFileSystemEntries(UpperCasePath);
+            var lowerCaseResult = directoryService.GetFileSystemEntries(LowerCasePath);
+
+            Assert.Equal(_upperCaseFileSystemMetadata, upperCaseResult);
+            Assert.Equal(_lowerCaseFileSystemMetadata, lowerCaseResult);
+        }
+
+        [Fact]
+        public void GetFiles_GivenPathsWithDifferentCasing_ReturnsCorrectFiles()
+        {
+            var fileSystemMock = new Mock<IFileSystem>();
+            fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(_upperCaseFileSystemMetadata);
+            fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(_lowerCaseFileSystemMetadata);
+            var directoryService = new DirectoryService(fileSystemMock.Object);
+
+            var upperCaseResult = directoryService.GetFiles(UpperCasePath);
+            var lowerCaseResult = directoryService.GetFiles(LowerCasePath);
+
+            Assert.Equal(_upperCaseFileSystemMetadata.Where(f => !f.IsDirectory), upperCaseResult);
+            Assert.Equal(_lowerCaseFileSystemMetadata.Where(f => !f.IsDirectory), lowerCaseResult);
+        }
+
+        [Fact]
+        public void GetFile_GivenFilePathsWithDifferentCasing_ReturnsCorrectFile()
+        {
+            const string lowerCasePath = "/music/someartist/song 1.mp3";
+            var lowerCaseFileSystemMetadata = new FileSystemMetadata
+            {
+                FullName = lowerCasePath,
+                Exists = true
+            };
+            const string upperCasePath = "/music/SOMEARTIST/SONG 1.mp3";
+            var upperCaseFileSystemMetadata = new FileSystemMetadata
+            {
+                FullName = upperCasePath,
+                Exists = false
+            };
+            var fileSystemMock = new Mock<IFileSystem>();
+            fileSystemMock.Setup(f => f.GetFileInfo(It.Is<string>(x => x == upperCasePath))).Returns(upperCaseFileSystemMetadata);
+            fileSystemMock.Setup(f => f.GetFileInfo(It.Is<string>(x => x == lowerCasePath))).Returns(lowerCaseFileSystemMetadata);
+            var directoryService = new DirectoryService(fileSystemMock.Object);
+
+            var lowerCaseResult = directoryService.GetFile(lowerCasePath);
+            var upperCaseResult = directoryService.GetFile(upperCasePath);
+
+            Assert.Equal(lowerCaseFileSystemMetadata, lowerCaseResult);
+            Assert.Null(upperCaseResult);
+        }
+
+        [Fact]
+        public void GetFile_GivenCachedPath_ReturnsCachedFile()
+        {
+            const string path = "/music/someartist/song 1.mp3";
+            var cachedFileSystemMetadata = new FileSystemMetadata
+            {
+                FullName = path,
+                Exists = true
+            };
+            var newFileSystemMetadata = new FileSystemMetadata
+            {
+                FullName = "/music/SOMEARTIST/song 1.mp3",
+                Exists = true
+            };
+
+            var fileSystemMock = new Mock<IFileSystem>();
+            fileSystemMock.Setup(f => f.GetFileInfo(It.Is<string>(x => x == path))).Returns(cachedFileSystemMetadata);
+            var directoryService = new DirectoryService(fileSystemMock.Object);
+
+            var result = directoryService.GetFile(path);
+            fileSystemMock.Setup(f => f.GetFileInfo(It.Is<string>(x => x == path))).Returns(newFileSystemMetadata);
+            var secondResult = directoryService.GetFile(path);
+
+            Assert.Equal(cachedFileSystemMetadata, result);
+            Assert.Equal(cachedFileSystemMetadata, secondResult);
+        }
+
+        [Fact]
+        public void GetFilePaths_GivenCachedFilePathWithoutClear_ReturnsOnlyCachedPaths()
+        {
+            const string path = "/music/someartist";
+
+            var cachedPaths = new[]
+            {
+                "/music/someartist/song 1.mp3",
+                "/music/someartist/song 2.mp3",
+                "/music/someartist/song 3.mp3",
+                "/music/someartist/song 4.mp3",
+            };
+            var newPaths = new[]
+            {
+                "/music/someartist/song 5.mp3",
+                "/music/someartist/song 6.mp3",
+                "/music/someartist/song 7.mp3",
+                "/music/someartist/song 8.mp3",
+            };
+
+            var fileSystemMock = new Mock<IFileSystem>();
+            fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths);
+            var directoryService = new DirectoryService(fileSystemMock.Object);
+
+            var result = directoryService.GetFilePaths(path);
+            fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths);
+            var secondResult = directoryService.GetFilePaths(path);
+
+            Assert.Equal(cachedPaths, result);
+            Assert.Equal(cachedPaths, secondResult);
+        }
+
+        [Fact]
+        public void GetFilePaths_GivenCachedFilePathWithClear_ReturnsNewPaths()
+        {
+            const string path = "/music/someartist";
+
+            var cachedPaths = new[]
+            {
+                "/music/someartist/song 1.mp3",
+                "/music/someartist/song 2.mp3",
+                "/music/someartist/song 3.mp3",
+                "/music/someartist/song 4.mp3",
+            };
+            var newPaths = new[]
+            {
+                "/music/someartist/song 5.mp3",
+                "/music/someartist/song 6.mp3",
+                "/music/someartist/song 7.mp3",
+                "/music/someartist/song 8.mp3",
+            };
+
+            var fileSystemMock = new Mock<IFileSystem>();
+            fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths);
+            var directoryService = new DirectoryService(fileSystemMock.Object);
+
+            var result = directoryService.GetFilePaths(path);
+            fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths);
+            var secondResult = directoryService.GetFilePaths(path, true);
+
+            Assert.Equal(cachedPaths, result);
+            Assert.Equal(newPaths, secondResult);
+        }
+    }
+}

+ 1 - 0
tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj

@@ -14,6 +14,7 @@
 
   <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
+    <PackageReference Include="Moq" Version="4.16.1" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
     <PackageReference Include="coverlet.collector" Version="1.3.0" />