< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.Providers.DirectoryService
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/Providers/DirectoryService.cs
Line coverage
99%
Covered lines: 102
Uncovered lines: 1
Coverable lines: 103
Total lines: 225
Line coverage: 99%
Branch coverage
88%
Covered branches: 37
Total branches: 42
Branch coverage: 88%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/16/2026 - 12:15:55 AM Line coverage: 100% (62/62) Branch coverage: 100% (24/24) Total lines: 1385/20/2026 - 12:15:44 AM Line coverage: 100% (62/62) Branch coverage: 95.8% (23/24) Total lines: 13810/29/2026 - 12:17:11 AM Line coverage: 99% (102/103) Branch coverage: 88% (37/42) Total lines: 225 5/16/2026 - 12:15:55 AM Line coverage: 100% (62/62) Branch coverage: 100% (24/24) Total lines: 1385/20/2026 - 12:15:44 AM Line coverage: 100% (62/62) Branch coverage: 95.8% (23/24) Total lines: 13810/29/2026 - 12:17:11 AM Line coverage: 99% (102/103) Branch coverage: 88% (37/42) Total lines: 225

Coverage delta

Coverage delta 8 -8

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetFileSystemEntries(...)100%11100%
GetDirectories(...)100%44100%
GetFiles(...)100%44100%
GetFile(...)100%44100%
GetDirectory(...)100%44100%
GetFileSystemEntry(...)75%88100%
GetFilePaths(...)100%11100%
GetFilePaths(...)100%44100%
Invalidate(...)50%22100%
Move(...)100%11100%
IsAccessible(...)100%11100%
DropCacheIfIdleOrFull()66.66%6691.66%
Forget(...)83.33%6685.71%

File(s)

/srv/git/jellyfin/MediaBrowser.Controller/Providers/DirectoryService.cs

#LineLine coverage
 1#pragma warning disable CS1591
 2
 3using System;
 4using System.Collections.Concurrent;
 5using System.Collections.Generic;
 6using System.IO;
 7using System.Linq;
 8using System.Threading;
 9using MediaBrowser.Model.IO;
 10
 11namespace MediaBrowser.Controller.Providers
 12{
 13    public class DirectoryService : IDirectoryService
 14    {
 15        // TODO replace with one shared bounded cache.
 16        private const int MaxCachedRecords = 100_000;
 17        private const int AccessIntervalMs = 1_000;
 18        // Timeout cache if no access for 5 minutes.
 19        private const int IdleTimeoutMs = 5 * 60 * 1_000;
 20
 35121        private readonly ConcurrentDictionary<string, FileSystemMetadata[]> _cache = new(StringComparer.Ordinal);
 22
 35123        private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache = new(StringComparer.Ordinal);
 24
 35125        private readonly ConcurrentDictionary<string, List<string>> _filePathCache = new(StringComparer.Ordinal);
 26
 27        private readonly IFileSystem _fileSystem;
 28
 29        // ConcurrentDictionary.Count locks the dictionary, so keep an estimated counter.
 30        // Concurrent factory runs can overcount and a clear racing an add can undercount,
 31        // it only has to be roughly right.
 32        private int _recordCount;
 35133        private long _lastAccess = Environment.TickCount64;
 34
 35        public DirectoryService(IFileSystem fileSystem)
 36        {
 35137            _fileSystem = fileSystem;
 35138        }
 39
 40        public FileSystemMetadata[] GetFileSystemEntries(string path)
 41        {
 67942            DropCacheIfIdleOrFull();
 43
 67944            return _cache.GetOrAdd(
 67945                path,
 67946                static (p, state) =>
 67947                {
 67948                    FileSystemMetadata[] entries;
 67949                    try
 67950                    {
 67951                        entries = state.FileSystem.GetFileSystemEntries(p).ToArray();
 67952                    }
 67953                    catch (DirectoryNotFoundException)
 67954                    {
 67955                        entries = [];
 67956                    }
 67957
 67958                    Interlocked.Add(ref state.Service._recordCount, entries.Length + 1);
 67959                    return entries;
 67960                },
 67961                (FileSystem: _fileSystem, Service: this));
 62        }
 63
 64        public List<FileSystemMetadata> GetDirectories(string path)
 65        {
 266            var list = new List<FileSystemMetadata>();
 267            var items = GetFileSystemEntries(path);
 1668            for (var i = 0; i < items.Length; i++)
 69            {
 670                var item = items[i];
 671                if (item.IsDirectory)
 72                {
 373                    list.Add(item);
 74                }
 75            }
 76
 277            return list;
 78        }
 79
 80        public List<FileSystemMetadata> GetFiles(string path)
 81        {
 282            var list = new List<FileSystemMetadata>();
 283            var items = GetFileSystemEntries(path);
 1684            for (var i = 0; i < items.Length; i++)
 85            {
 686                var item = items[i];
 687                if (!item.IsDirectory)
 88                {
 389                    list.Add(item);
 90                }
 91            }
 92
 293            return list;
 94        }
 95
 96        public FileSystemMetadata? GetFile(string path)
 97        {
 698            var entry = GetFileSystemEntry(path);
 699            return entry is not null && !entry.IsDirectory ? entry : null;
 100        }
 101
 102        public FileSystemMetadata? GetDirectory(string path)
 103        {
 4104            var entry = GetFileSystemEntry(path);
 4105            return entry is not null && entry.IsDirectory ? entry : null;
 106        }
 107
 108        public FileSystemMetadata? GetFileSystemEntry(string path)
 109        {
 59110            DropCacheIfIdleOrFull();
 111
 59112            if (!_fileCache.TryGetValue(path, out var result))
 113            {
 56114                var file = _fileSystem.GetFileSystemInfo(path);
 56115                if (file?.Exists ?? false)
 116                {
 50117                    result = file;
 50118                    if (_fileCache.TryAdd(path, result))
 119                    {
 50120                        Interlocked.Increment(ref _recordCount);
 121                    }
 122                }
 123            }
 124
 59125            return result;
 126        }
 127
 128        public IReadOnlyList<string> GetFilePaths(string path)
 5129            => GetFilePaths(path, false);
 130
 131        public IReadOnlyList<string> GetFilePaths(string path, bool clearCache)
 132        {
 7133            if (clearCache && _filePathCache.TryRemove(path, out var cached))
 134            {
 1135                Interlocked.Add(ref _recordCount, -(cached.Count + 1));
 136            }
 137
 7138            DropCacheIfIdleOrFull();
 139
 7140            var filePaths = _filePathCache.GetOrAdd(
 7141                path,
 7142                static (p, state) =>
 7143                {
 7144                    List<string> filePaths;
 7145                    try
 7146                    {
 7147                        filePaths = state.FileSystem.GetFilePaths(p).OrderBy(x => x).ToList();
 7148                    }
 7149                    catch (DirectoryNotFoundException)
 7150                    {
 7151                        filePaths = [];
 7152                    }
 7153
 7154                    Interlocked.Add(ref state.Service._recordCount, filePaths.Count + 1);
 7155                    return filePaths;
 7156                },
 7157                (FileSystem: _fileSystem, Service: this));
 158
 7159            return filePaths;
 160        }
 161
 162        public void Invalidate(string path)
 163        {
 67164            Forget(path);
 165
 67166            var parent = Path.GetDirectoryName(path);
 67167            if (!string.IsNullOrEmpty(parent))
 168            {
 67169                Forget(parent);
 170            }
 67171        }
 172
 173        public void Move(string source, string destination)
 174        {
 3175            Directory.Move(source, destination);
 176
 3177            Invalidate(source);
 3178            Invalidate(destination);
 3179        }
 180
 181        public bool IsAccessible(string path)
 182        {
 49183            return _fileSystem.GetFileSystemEntryPaths(path).Any();
 184        }
 185
 186        private void DropCacheIfIdleOrFull()
 187        {
 745188            var nowMs = Environment.TickCount64;
 745189            var idleMs = nowMs - _lastAccess;
 190
 745191            if (idleMs >= IdleTimeoutMs || _recordCount >= MaxCachedRecords)
 192            {
 3193                _cache.Clear();
 3194                _fileCache.Clear();
 3195                _filePathCache.Clear();
 3196                _recordCount = 0;
 3197                _lastAccess = nowMs;
 3198                return;
 199            }
 200
 742201            if (idleMs >= AccessIntervalMs)
 202            {
 2203                _lastAccess = nowMs;
 204            }
 742205        }
 206
 207        private void Forget(string path)
 208        {
 134209            if (_cache.TryRemove(path, out var entries))
 210            {
 54211                Interlocked.Add(ref _recordCount, -(entries.Length + 1));
 212            }
 213
 134214            if (_fileCache.TryRemove(path, out _))
 215            {
 0216                Interlocked.Decrement(ref _recordCount);
 217            }
 218
 134219            if (_filePathCache.TryRemove(path, out var filePaths))
 220            {
 1221                Interlocked.Add(ref _recordCount, -(filePaths.Count + 1));
 222            }
 134223        }
 224    }
 225}