< Summary - Jellyfin

Information
Class: Emby.Server.Implementations.IO.ManagedFileSystem
Assembly: Emby.Server.Implementations
File(s): /srv/git/jellyfin/Emby.Server.Implementations/IO/ManagedFileSystem.cs
Line coverage
59%
Covered lines: 160
Uncovered lines: 108
Coverable lines: 268
Total lines: 729
Line coverage: 59.7%
Branch coverage
54%
Covered branches: 60
Total branches: 110
Branch coverage: 54.5%
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: 56% (149/266) Branch coverage: 50.9% (52/102) Total lines: 7225/20/2026 - 12:15:44 AM Line coverage: 56% (149/266) Branch coverage: 47% (48/102) Total lines: 7227/27/2026 - 12:16:14 AM Line coverage: 57.5% (153/266) Branch coverage: 50% (51/102) Total lines: 7227/28/2026 - 12:13:49 AM Line coverage: 56% (149/266) Branch coverage: 47% (48/102) Total lines: 7227/30/2026 - 12:15:56 AM Line coverage: 57.5% (153/266) Branch coverage: 50% (51/102) Total lines: 7227/31/2026 - 12:16:28 AM Line coverage: 56% (149/266) Branch coverage: 47% (48/102) Total lines: 7228/5/2026 - 12:17:09 AM Line coverage: 57.5% (153/266) Branch coverage: 50% (51/102) Total lines: 7228/7/2026 - 12:16:14 AM Line coverage: 56% (149/266) Branch coverage: 47% (48/102) Total lines: 7228/8/2026 - 12:17:31 AM Line coverage: 57.5% (153/266) Branch coverage: 50% (51/102) Total lines: 7228/10/2026 - 12:14:18 AM Line coverage: 56% (149/266) Branch coverage: 47% (48/102) Total lines: 72210/29/2026 - 12:17:11 AM Line coverage: 59.7% (160/268) Branch coverage: 54.5% (60/110) Total lines: 72910/31/2026 - 12:17:46 AM Line coverage: 58.9% (158/268) Branch coverage: 52.7% (58/110) Total lines: 72911/1/2026 - 12:15:27 AM Line coverage: 59.7% (160/268) Branch coverage: 54.5% (60/110) Total lines: 72911/2/2026 - 12:18:06 AM Line coverage: 58.9% (158/268) Branch coverage: 52.7% (58/110) Total lines: 72911/4/2026 - 12:18:26 AM Line coverage: 59.7% (160/268) Branch coverage: 54.5% (60/110) Total lines: 729 5/16/2026 - 12:15:55 AM Line coverage: 56% (149/266) Branch coverage: 50.9% (52/102) Total lines: 7225/20/2026 - 12:15:44 AM Line coverage: 56% (149/266) Branch coverage: 47% (48/102) Total lines: 7227/27/2026 - 12:16:14 AM Line coverage: 57.5% (153/266) Branch coverage: 50% (51/102) Total lines: 7227/28/2026 - 12:13:49 AM Line coverage: 56% (149/266) Branch coverage: 47% (48/102) Total lines: 7227/30/2026 - 12:15:56 AM Line coverage: 57.5% (153/266) Branch coverage: 50% (51/102) Total lines: 7227/31/2026 - 12:16:28 AM Line coverage: 56% (149/266) Branch coverage: 47% (48/102) Total lines: 7228/5/2026 - 12:17:09 AM Line coverage: 57.5% (153/266) Branch coverage: 50% (51/102) Total lines: 7228/7/2026 - 12:16:14 AM Line coverage: 56% (149/266) Branch coverage: 47% (48/102) Total lines: 7228/8/2026 - 12:17:31 AM Line coverage: 57.5% (153/266) Branch coverage: 50% (51/102) Total lines: 7228/10/2026 - 12:14:18 AM Line coverage: 56% (149/266) Branch coverage: 47% (48/102) Total lines: 72210/29/2026 - 12:17:11 AM Line coverage: 59.7% (160/268) Branch coverage: 54.5% (60/110) Total lines: 72910/31/2026 - 12:17:46 AM Line coverage: 58.9% (158/268) Branch coverage: 52.7% (58/110) Total lines: 72911/1/2026 - 12:15:27 AM Line coverage: 59.7% (160/268) Branch coverage: 54.5% (60/110) Total lines: 72911/2/2026 - 12:18:06 AM Line coverage: 58.9% (158/268) Branch coverage: 52.7% (58/110) Total lines: 72911/4/2026 - 12:18:26 AM Line coverage: 59.7% (160/268) Branch coverage: 54.5% (60/110) Total lines: 729

Coverage delta

Coverage delta 8 -8

Metrics

File(s)

/srv/git/jellyfin/Emby.Server.Implementations/IO/ManagedFileSystem.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Globalization;
 4using System.IO;
 5using System.Linq;
 6using System.Security;
 7using Jellyfin.Extensions;
 8using MediaBrowser.Common.Configuration;
 9using MediaBrowser.Controller.IO;
 10using MediaBrowser.Model.IO;
 11using Microsoft.Extensions.Logging;
 12
 13namespace Emby.Server.Implementations.IO
 14{
 15    /// <summary>
 16    /// Class ManagedFileSystem.
 17    /// </summary>
 18    public class ManagedFileSystem : IFileSystem
 19    {
 220        private static readonly bool _isEnvironmentCaseInsensitive = OperatingSystem.IsWindows();
 221        private static readonly char[] _invalidPathCharacters =
 222        {
 223            '\"', '<', '>', '|', '\0',
 224            (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
 225            (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
 226            (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
 227            (char)31, ':', '*', '?', '\\', '/'
 228        };
 29
 30        private readonly ILogger<ManagedFileSystem> _logger;
 31        private readonly List<IShortcutHandler> _shortcutHandlers;
 32        private readonly string _tempPath;
 33
 34        /// <summary>
 35        /// Initializes a new instance of the <see cref="ManagedFileSystem"/> class.
 36        /// </summary>
 37        /// <param name="logger">The <see cref="ILogger"/> instance to use.</param>
 38        /// <param name="applicationPaths">The <see cref="IApplicationPaths"/> instance to use.</param>
 39        /// <param name="shortcutHandlers">the <see cref="IShortcutHandler"/>'s to use.</param>
 40        public ManagedFileSystem(
 41            ILogger<ManagedFileSystem> logger,
 42            IApplicationPaths applicationPaths,
 43            IEnumerable<IShortcutHandler> shortcutHandlers)
 44        {
 10245            _logger = logger;
 10246            _tempPath = applicationPaths.TempDirectory;
 10247            _shortcutHandlers = shortcutHandlers.ToList();
 10248        }
 49
 50        /// <summary>
 51        /// Determines whether the specified filename is shortcut.
 52        /// </summary>
 53        /// <param name="filename">The filename.</param>
 54        /// <returns><c>true</c> if the specified filename is shortcut; otherwise, <c>false</c>.</returns>
 55        /// <exception cref="ArgumentNullException"><paramref name="filename"/> is <c>null</c>.</exception>
 56        public virtual bool IsShortcut(string filename)
 57        {
 33058            ArgumentException.ThrowIfNullOrEmpty(filename);
 59
 33060            var extension = Path.GetExtension(filename);
 33061            return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase))
 62        }
 63
 64        /// <summary>
 65        /// Resolves the shortcut.
 66        /// </summary>
 67        /// <param name="filename">The filename.</param>
 68        /// <returns>System.String.</returns>
 69        /// <exception cref="ArgumentNullException"><paramref name="filename"/> is <c>null</c>.</exception>
 70        public virtual string? ResolveShortcut(string filename)
 71        {
 072            ArgumentException.ThrowIfNullOrEmpty(filename);
 73
 074            var extension = Path.GetExtension(filename);
 075            var handler = _shortcutHandlers.Find(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgno
 76
 077            return handler?.Resolve(filename);
 78        }
 79
 80        /// <inheritdoc />
 81        public virtual string MakeAbsolutePath(string folderPath, string filePath)
 82        {
 83            // path is actually a stream
 484            if (string.IsNullOrWhiteSpace(filePath))
 85            {
 086                return filePath;
 87            }
 88
 489            var isAbsolutePath = Path.IsPathRooted(filePath) && (!OperatingSystem.IsWindows() || filePath[0] != '\\');
 90
 491            if (isAbsolutePath)
 92            {
 93                // absolute local path
 194                return filePath;
 95            }
 96
 97            // unc path
 398            if (filePath.StartsWith(@"\\", StringComparison.Ordinal))
 99            {
 0100                return filePath;
 101            }
 102
 3103            var filePathSpan = filePath.AsSpan();
 104
 105            // relative path on windows
 3106            if (filePath[0] == '\\')
 107            {
 0108                filePathSpan = filePathSpan.Slice(1);
 109            }
 110
 111            try
 112            {
 3113                return Path.GetFullPath(Path.Join(folderPath, filePathSpan));
 114            }
 0115            catch (ArgumentException)
 116            {
 0117                return filePath;
 118            }
 0119            catch (PathTooLongException)
 120            {
 0121                return filePath;
 122            }
 0123            catch (NotSupportedException)
 124            {
 0125                return filePath;
 126            }
 3127        }
 128
 129        /// <summary>
 130        /// Creates the shortcut.
 131        /// </summary>
 132        /// <param name="shortcutPath">The shortcut path.</param>
 133        /// <param name="target">The target.</param>
 134        /// <exception cref="ArgumentNullException">The shortcutPath or target is null.</exception>
 135        public virtual void CreateShortcut(string shortcutPath, string target)
 136        {
 0137            ArgumentException.ThrowIfNullOrEmpty(shortcutPath);
 0138            ArgumentException.ThrowIfNullOrEmpty(target);
 139
 0140            var extension = Path.GetExtension(shortcutPath);
 0141            var handler = _shortcutHandlers.Find(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgno
 142
 0143            if (handler is not null)
 144            {
 0145                handler.Create(shortcutPath, target);
 146            }
 147            else
 148            {
 0149                throw new NotImplementedException();
 150            }
 151        }
 152
 153        /// <inheritdoc />
 154        public void MoveDirectory(string source, string destination)
 155        {
 156            // Make sure parent directory of target exists
 2157            var parent = Directory.GetParent(destination);
 2158            parent?.Create();
 159
 160            try
 161            {
 2162                Directory.Move(source, destination);
 1163            }
 1164            catch (IOException)
 165            {
 166                // Cross device move requires a copy
 1167                Directory.CreateDirectory(destination);
 1168                var sourceDir = new DirectoryInfo(source);
 6169                foreach (var file in sourceDir.EnumerateFiles())
 170                {
 2171                    file.CopyTo(Path.Combine(destination, file.Name), true);
 172                }
 173
 1174                sourceDir.Delete(true);
 1175            }
 2176        }
 177
 178        /// <summary>
 179        /// Returns a <see cref="FileSystemMetadata"/> object for the specified file or directory path.
 180        /// </summary>
 181        /// <param name="path">A path to a file or directory.</param>
 182        /// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
 183        /// <remarks>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> object's
 184        /// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and all other properties will refl
 185        public virtual FileSystemMetadata GetFileSystemInfo(string path)
 186        {
 187            // Take a guess to try and avoid two file system hits, but we'll double-check by calling Exists
 159188            if (Path.HasExtension(path))
 189            {
 0190                var fileInfo = new FileInfo(path);
 191
 0192                if (fileInfo.Exists)
 193                {
 0194                    return GetFileSystemMetadata(fileInfo);
 195                }
 196
 0197                return GetFileSystemMetadata(new DirectoryInfo(path));
 198            }
 199            else
 200            {
 159201                var fileInfo = new DirectoryInfo(path);
 202
 159203                if (fileInfo.Exists)
 204                {
 158205                    return GetFileSystemMetadata(fileInfo);
 206                }
 207
 1208                return GetFileSystemMetadata(new FileInfo(path));
 209            }
 210        }
 211
 212        /// <summary>
 213        /// Returns a <see cref="FileSystemMetadata"/> object for the specified file path.
 214        /// </summary>
 215        /// <param name="path">A path to a file.</param>
 216        /// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
 217        /// <remarks><para>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> ob
 218        /// <see cref="FileSystemMetadata.IsDirectory"/> property and the <see cref="FileSystemMetadata.Exists"/> proper
 219        /// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></r
 220        public virtual FileSystemMetadata GetFileInfo(string path)
 221        {
 1222            var fileInfo = new FileInfo(path);
 223
 1224            return GetFileSystemMetadata(fileInfo);
 225        }
 226
 227        /// <summary>
 228        /// Returns a <see cref="FileSystemMetadata"/> object for the specified directory path.
 229        /// </summary>
 230        /// <param name="path">A path to a directory.</param>
 231        /// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
 232        /// <remarks><para>If the specified path points to a file, the returned <see cref="FileSystemMetadata"/> object'
 233        /// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and the <see cref="FileSystemMetad
 234        /// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></r
 235        public virtual FileSystemMetadata GetDirectoryInfo(string path)
 236        {
 219237            var fileInfo = new DirectoryInfo(path);
 238
 219239            return GetFileSystemMetadata(fileInfo);
 240        }
 241
 242        private FileSystemMetadata GetFileSystemMetadata(FileSystemInfo info)
 243        {
 762244            var result = new FileSystemMetadata
 762245            {
 762246                Exists = info.Exists,
 762247                FullName = info.FullName,
 762248                Extension = info.Extension,
 762249                Name = info.Name
 762250            };
 251
 762252            if (result.Exists)
 253            {
 761254                result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttrib
 255
 761256                if (info is FileInfo fileInfo)
 257                {
 168258                    result.CreationTimeUtc = GetCreationTimeUtc(info);
 168259                    result.LastWriteTimeUtc = GetLastWriteTimeUtc(info);
 168260                    if (fileInfo.LinkTarget is not null)
 261                    {
 262                        try
 263                        {
 1264                            var targetFileInfo = FileSystemHelper.ResolveLinkTarget(fileInfo, returnFinalTarget: true);
 1265                            if (targetFileInfo is not null)
 266                            {
 1267                                result.Exists = targetFileInfo.Exists;
 1268                                if (result.Exists)
 269                                {
 0270                                    result.Length = targetFileInfo.Length;
 0271                                    result.CreationTimeUtc = GetCreationTimeUtc(targetFileInfo);
 0272                                    result.LastWriteTimeUtc = GetLastWriteTimeUtc(targetFileInfo);
 273                                }
 274                            }
 275                            else
 276                            {
 0277                                result.Exists = false;
 278                            }
 1279                        }
 0280                        catch (UnauthorizedAccessException ex)
 281                        {
 0282                            _logger.LogError(ex, "Reading the file at {Path} failed due to a permissions exception.", fi
 0283                        }
 284                    }
 285                    else
 286                    {
 167287                        result.Length = fileInfo.Length;
 288                    }
 289                }
 290            }
 291            else
 292            {
 1293                result.IsDirectory = info is DirectoryInfo;
 294            }
 295
 762296            return result;
 297        }
 298
 299        /// <summary>
 300        /// Takes a filename and removes invalid characters.
 301        /// </summary>
 302        /// <param name="filename">The filename.</param>
 303        /// <returns>System.String.</returns>
 304        /// <exception cref="ArgumentNullException">The filename is null.</exception>
 305        public string GetValidFilename(string filename)
 306        {
 10307            var first = filename.IndexOfAny(_invalidPathCharacters);
 10308            if (first == -1)
 309            {
 310                // Fast path for clean strings
 7311                return filename;
 312            }
 313
 3314            return string.Create(
 3315                filename.Length,
 3316                (filename, _invalidPathCharacters, first),
 3317                (chars, state) =>
 3318                {
 3319                    state.filename.AsSpan().CopyTo(chars);
 3320
 3321                    chars[state.first++] = ' ';
 3322
 3323                    var len = chars.Length;
 3324                    foreach (var c in state._invalidPathCharacters)
 3325                    {
 3326                        for (int i = state.first; i < len; i++)
 3327                        {
 3328                            if (chars[i] == c)
 3329                            {
 3330                                chars[i] = ' ';
 3331                            }
 3332                        }
 3333                    }
 3334                });
 335        }
 336
 337        /// <summary>
 338        /// Gets the creation time UTC.
 339        /// </summary>
 340        /// <param name="info">The info.</param>
 341        /// <returns>DateTime.</returns>
 342        public DateTime GetCreationTimeUtc(FileSystemInfo info)
 343        {
 344            // This could throw an error on some file systems that have dates out of range
 345            try
 346            {
 168347                return info.CreationTimeUtc;
 348            }
 0349            catch (Exception ex)
 350            {
 0351                _logger.LogError(ex, "Error determining CreationTimeUtc for {FullName}", info.FullName);
 0352                return DateTime.MinValue;
 353            }
 168354        }
 355
 356        /// <inheritdoc />
 357        public virtual DateTime GetCreationTimeUtc(string path)
 358        {
 0359            return GetCreationTimeUtc(GetFileSystemInfo(path));
 360        }
 361
 362        /// <inheritdoc />
 363        public virtual DateTime GetCreationTimeUtc(FileSystemMetadata info)
 364        {
 0365            return info.CreationTimeUtc;
 366        }
 367
 368        /// <inheritdoc />
 369        public virtual DateTime GetLastWriteTimeUtc(FileSystemMetadata info)
 370        {
 3371            return info.LastWriteTimeUtc;
 372        }
 373
 374        /// <summary>
 375        /// Gets the creation time UTC.
 376        /// </summary>
 377        /// <param name="info">The info.</param>
 378        /// <returns>DateTime.</returns>
 379        public DateTime GetLastWriteTimeUtc(FileSystemInfo info)
 380        {
 381            // This could throw an error on some file systems that have dates out of range
 382            try
 383            {
 168384                return info.LastWriteTimeUtc;
 385            }
 0386            catch (Exception ex)
 387            {
 0388                _logger.LogError(ex, "Error determining LastAccessTimeUtc for {FullName}", info.FullName);
 0389                return DateTime.MinValue;
 390            }
 168391        }
 392
 393        /// <inheritdoc />
 394        public virtual DateTime GetLastWriteTimeUtc(string path)
 395        {
 0396            return GetLastWriteTimeUtc(GetFileSystemInfo(path));
 397        }
 398
 399        /// <inheritdoc />
 400        public virtual void SetHidden(string path, bool isHidden)
 401        {
 0402            if (!OperatingSystem.IsWindows())
 403            {
 0404                return;
 405            }
 406
 0407            var info = new FileInfo(path);
 408
 0409            if (info.Exists &&
 0410                (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden != isHidden)
 411            {
 0412                if (isHidden)
 413                {
 0414                    File.SetAttributes(path, info.Attributes | FileAttributes.Hidden);
 415                }
 416                else
 417                {
 0418                    File.SetAttributes(path, info.Attributes & ~FileAttributes.Hidden);
 419                }
 420            }
 0421        }
 422
 423        /// <inheritdoc />
 424        public virtual void SetAttributes(string path, bool isHidden, bool readOnly)
 425        {
 2426            if (!OperatingSystem.IsWindows())
 427            {
 2428                return;
 429            }
 430
 0431            var info = new FileInfo(path);
 432
 0433            if (!info.Exists)
 434            {
 0435                return;
 436            }
 437
 0438            if ((info.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly == readOnly
 0439                && (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden == isHidden)
 440            {
 0441                return;
 442            }
 443
 0444            var attributes = info.Attributes;
 445
 0446            if (readOnly)
 447            {
 0448                attributes |= FileAttributes.ReadOnly;
 449            }
 450            else
 451            {
 0452                attributes &= ~FileAttributes.ReadOnly;
 453            }
 454
 0455            if (isHidden)
 456            {
 0457                attributes |= FileAttributes.Hidden;
 458            }
 459            else
 460            {
 0461                attributes &= ~FileAttributes.Hidden;
 462            }
 463
 0464            File.SetAttributes(path, attributes);
 0465        }
 466
 467        /// <inheritdoc />
 468        public virtual void SwapFiles(string file1, string file2)
 469        {
 0470            ArgumentException.ThrowIfNullOrEmpty(file1);
 0471            ArgumentException.ThrowIfNullOrEmpty(file2);
 472
 0473            var temp1 = Path.Combine(_tempPath, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture));
 474
 475            // Copying over will fail against hidden files
 0476            SetHidden(file1, false);
 0477            SetHidden(file2, false);
 478
 0479            Directory.CreateDirectory(_tempPath);
 0480            File.Copy(file1, temp1, true);
 481
 0482            File.Copy(file2, file1, true);
 0483            File.Move(temp1, file2, true);
 0484        }
 485
 486        /// <inheritdoc />
 487        public virtual bool ContainsSubPath(string parentPath, string path)
 488        {
 11489            ArgumentException.ThrowIfNullOrEmpty(parentPath);
 11490            ArgumentException.ThrowIfNullOrEmpty(path);
 491
 11492            var parent = Path.TrimEndingDirectorySeparator(parentPath);
 493
 494            // The parent has to be an anchored prefix of the path, otherwise unrelated paths that merely
 495            // contain the parent as a segment (e.g. /media and /data/media/tv) would be treated as related.
 11496            return path.Length > parent.Length
 11497                   && path.StartsWith(parent, _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : Strin
 11498                   && (Path.EndsInDirectorySeparator(parent) || IsDirectorySeparator(path[parent.Length]));
 499        }
 500
 501        private static bool IsDirectorySeparator(char c)
 6502            => c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
 503
 504        /// <inheritdoc />
 505        public virtual bool AreEqual(string path1, string path2)
 506        {
 307507            if (string.IsNullOrWhiteSpace(path1) || string.IsNullOrWhiteSpace(path2))
 508            {
 0509                return false;
 510            }
 511
 307512            var normalized1 = Path.TrimEndingDirectorySeparator(path1);
 307513            var normalized2 = Path.TrimEndingDirectorySeparator(path2);
 514
 307515            return string.Equals(
 307516                normalized1,
 307517                normalized2,
 307518                _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
 519        }
 520
 521        /// <inheritdoc />
 522        public virtual string GetFileNameWithoutExtension(FileSystemMetadata info)
 523        {
 4524            if (info.IsDirectory)
 525            {
 0526                return info.Name;
 527            }
 528
 4529            return Path.GetFileNameWithoutExtension(info.FullName);
 530        }
 531
 532        /// <inheritdoc />
 533        public virtual bool IsPathFile(string path)
 534        {
 3514535            if (path.Contains("://", StringComparison.OrdinalIgnoreCase)
 3514536                && !path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
 537            {
 0538                return false;
 539            }
 540
 3514541            return true;
 542        }
 543
 544        /// <inheritdoc />
 545        public virtual void DeleteFile(string path)
 546        {
 2547            SetAttributes(path, false, false);
 2548            File.Delete(path);
 2549        }
 550
 551        /// <inheritdoc />
 552        public virtual IEnumerable<FileSystemMetadata> GetDrives()
 553        {
 554            // check for ready state to avoid waiting for drives to timeout
 555            // some drives on linux have no actual size or are used for other purposes
 0556            return DriveInfo.GetDrives()
 0557                .Where(
 0558                    d => (d.DriveType == DriveType.Fixed || d.DriveType == DriveType.Network || d.DriveType == DriveType
 0559                         && d.IsReady
 0560                         && d.TotalSize != 0)
 0561                .Select(d => new FileSystemMetadata
 0562                {
 0563                    Name = d.Name,
 0564                    FullName = d.RootDirectory.FullName,
 0565                    IsDirectory = true
 0566                });
 567        }
 568
 569        /// <inheritdoc />
 570        public virtual IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
 571        {
 0572            return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", GetEnumerationOptions(recursive)));
 573        }
 574
 575        /// <inheritdoc />
 576        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false)
 577        {
 3578            return GetFiles(path, "*", recursive);
 579        }
 580
 581        /// <inheritdoc />
 582        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, string searchPattern, bool recursive = fals
 583        {
 3584            return GetFiles(path, searchPattern, null, false, recursive);
 585        }
 586
 587        /// <inheritdoc />
 588        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string>? extensions, bool ena
 589        {
 44590            return GetFiles(path, "*", extensions, enableCaseSensitiveExtensions, recursive);
 591        }
 592
 593        /// <inheritdoc />
 594        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, string searchPattern, IReadOnlyList<string>
 595        {
 47596            if (!Directory.Exists(path))
 597            {
 0598                _logger.LogWarning("Directory does not exist: {Path}", path);
 0599                return [];
 600            }
 601
 47602            var enumerationOptions = GetEnumerationOptions(recursive);
 603
 604            // On linux and macOS the search pattern is case-sensitive
 605            // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
 47606            if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions is not null && extensions
 607            {
 0608                searchPattern = searchPattern.EndsWith(extensions[0], StringComparison.Ordinal) ? searchPattern : search
 609
 0610                return ToMetadata(new DirectoryInfo(path).EnumerateFiles(searchPattern, enumerationOptions));
 611            }
 612
 47613            var files = new DirectoryInfo(path).EnumerateFiles(searchPattern, enumerationOptions);
 614
 47615            if (extensions is not null && extensions.Count > 0)
 616            {
 44617                files = files.Where(i =>
 44618                {
 44619                    var ext = i.Extension.AsSpan();
 44620                    if (ext.IsEmpty)
 44621                    {
 44622                        return false;
 44623                    }
 44624
 44625                    return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase);
 44626                });
 627            }
 628
 47629            return ToMetadata(files);
 630        }
 631
 632        /// <inheritdoc />
 633        public virtual IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
 634        {
 635            // Note: any of unhandled exceptions thrown by this method may cause the caller to believe the whole path is
 636            // But what causing the exception may be a single file under that path. This could lead to unexpected behavi
 637            // For example, the scanner will remove everything in that path due to unhandled errors.
 460638            var directoryInfo = new DirectoryInfo(path);
 460639            var enumerationOptions = GetEnumerationOptions(recursive);
 640
 460641            return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", enumerationOptions));
 642        }
 643
 644        private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos)
 645        {
 507646            return infos.Select(GetFileSystemMetadata);
 647        }
 648
 649        /// <inheritdoc />
 650        public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
 651        {
 33652            return Directory.EnumerateDirectories(path, "*", GetEnumerationOptions(recursive));
 653        }
 654
 655        /// <inheritdoc />
 656        public virtual IEnumerable<string> GetFilePaths(string path, bool recursive = false)
 657        {
 3658            return GetFilePaths(path, null, false, recursive);
 659        }
 660
 661        /// <inheritdoc />
 662        public virtual IEnumerable<string> GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExten
 663        {
 4664            var enumerationOptions = GetEnumerationOptions(recursive);
 665
 666            // On linux and macOS the search pattern is case-sensitive
 667            // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
 4668            if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions is not null && extensions
 669            {
 1670                return Directory.EnumerateFiles(path, "*" + extensions[0], enumerationOptions);
 671            }
 672
 3673            var files = Directory.EnumerateFiles(path, "*", enumerationOptions);
 674
 3675            if (extensions is not null && extensions.Length > 0)
 676            {
 0677                files = files.Where(i =>
 0678                {
 0679                    var ext = Path.GetExtension(i.AsSpan());
 0680                    if (ext.IsEmpty)
 0681                    {
 0682                        return false;
 0683                    }
 0684
 0685                    return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase);
 0686                });
 687            }
 688
 3689            return files;
 690        }
 691
 692        /// <inheritdoc />
 693        public virtual IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
 694        {
 695            try
 696            {
 49697                return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive));
 698            }
 0699            catch (Exception ex) when (ex is UnauthorizedAccessException or DirectoryNotFoundException or SecurityExcept
 700            {
 0701                _logger.LogWarning("Failed to enumerate path \"{Path}\": {Message}", path, ex.Message);
 0702                return Enumerable.Empty<string>();
 703            }
 49704        }
 705
 706        /// <inheritdoc />
 707        public virtual bool DirectoryExists(string path)
 708        {
 0709            return Directory.Exists(path);
 710        }
 711
 712        /// <inheritdoc />
 713        public virtual bool FileExists(string path)
 714        {
 0715            return File.Exists(path);
 716        }
 717
 718        private EnumerationOptions GetEnumerationOptions(bool recursive)
 719        {
 593720            return new EnumerationOptions
 593721            {
 593722                RecurseSubdirectories = recursive,
 593723                IgnoreInaccessible = true,
 593724                // Don't skip any files.
 593725                AttributesToSkip = 0
 593726            };
 727        }
 728    }
 729}

Methods/Properties

.cctor()
.ctor(Microsoft.Extensions.Logging.ILogger`1<Emby.Server.Implementations.IO.ManagedFileSystem>,MediaBrowser.Common.Configuration.IApplicationPaths,System.Collections.Generic.IEnumerable`1<MediaBrowser.Model.IO.IShortcutHandler>)
IsShortcut(System.String)
ResolveShortcut(System.String)
MakeAbsolutePath(System.String,System.String)
CreateShortcut(System.String,System.String)
MoveDirectory(System.String,System.String)
GetFileSystemInfo(System.String)
GetFileInfo(System.String)
GetDirectoryInfo(System.String)
GetFileSystemMetadata(System.IO.FileSystemInfo)
GetValidFilename(System.String)
GetCreationTimeUtc(System.IO.FileSystemInfo)
GetCreationTimeUtc(System.String)
GetCreationTimeUtc(MediaBrowser.Model.IO.FileSystemMetadata)
GetLastWriteTimeUtc(MediaBrowser.Model.IO.FileSystemMetadata)
GetLastWriteTimeUtc(System.IO.FileSystemInfo)
GetLastWriteTimeUtc(System.String)
SetHidden(System.String,System.Boolean)
SetAttributes(System.String,System.Boolean,System.Boolean)
SwapFiles(System.String,System.String)
ContainsSubPath(System.String,System.String)
IsDirectorySeparator(System.Char)
AreEqual(System.String,System.String)
GetFileNameWithoutExtension(MediaBrowser.Model.IO.FileSystemMetadata)
IsPathFile(System.String)
DeleteFile(System.String)
GetDrives()
GetDirectories(System.String,System.Boolean)
GetFiles(System.String,System.Boolean)
GetFiles(System.String,System.String,System.Boolean)
GetFiles(System.String,System.Collections.Generic.IReadOnlyList`1<System.String>,System.Boolean,System.Boolean)
GetFiles(System.String,System.String,System.Collections.Generic.IReadOnlyList`1<System.String>,System.Boolean,System.Boolean)
GetFileSystemEntries(System.String,System.Boolean)
ToMetadata(System.Collections.Generic.IEnumerable`1<System.IO.FileSystemInfo>)
GetDirectoryPaths(System.String,System.Boolean)
GetFilePaths(System.String,System.Boolean)
GetFilePaths(System.String,System.String[],System.Boolean,System.Boolean)
GetFileSystemEntryPaths(System.String,System.Boolean)
DirectoryExists(System.String)
FileExists(System.String)
GetEnumerationOptions(System.Boolean)