< 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
56%
Covered lines: 149
Uncovered lines: 117
Coverable lines: 266
Total lines: 722
Line coverage: 56%
Branch coverage
50%
Covered branches: 52
Total branches: 102
Branch coverage: 50.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 1/23/2026 - 12:11:06 AM Line coverage: 58.5% (154/263) Branch coverage: 53% (53/100) Total lines: 7161/29/2026 - 12:13:32 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 7162/3/2026 - 12:13:02 AM Line coverage: 58.5% (154/263) Branch coverage: 53% (53/100) Total lines: 7162/4/2026 - 12:13:52 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 7162/6/2026 - 12:13:21 AM Line coverage: 58.5% (154/263) Branch coverage: 53% (53/100) Total lines: 7162/8/2026 - 12:14:11 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 7163/3/2026 - 12:13:24 AM Line coverage: 58.5% (154/263) Branch coverage: 53% (53/100) Total lines: 7163/4/2026 - 12:14:01 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 7163/28/2026 - 12:14:04 AM Line coverage: 58.5% (154/263) Branch coverage: 53% (53/100) Total lines: 7163/29/2026 - 12:13:48 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 7164/1/2026 - 12:13:37 AM Line coverage: 58.5% (154/263) Branch coverage: 53% (53/100) Total lines: 7164/2/2026 - 12:14:03 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 7164/12/2026 - 12:13:54 AM Line coverage: 58.9% (155/263) Branch coverage: 54% (54/100) Total lines: 7164/13/2026 - 12:14:08 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 7164/28/2026 - 12:13:10 AM Line coverage: 58.5% (154/263) Branch coverage: 53% (53/100) Total lines: 7164/29/2026 - 12:14:58 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 7165/4/2026 - 12:15:16 AM Line coverage: 56% (149/266) Branch coverage: 50.9% (52/102) Total lines: 722 1/23/2026 - 12:11:06 AM Line coverage: 58.5% (154/263) Branch coverage: 53% (53/100) Total lines: 7161/29/2026 - 12:13:32 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 7162/3/2026 - 12:13:02 AM Line coverage: 58.5% (154/263) Branch coverage: 53% (53/100) Total lines: 7162/4/2026 - 12:13:52 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 7162/6/2026 - 12:13:21 AM Line coverage: 58.5% (154/263) Branch coverage: 53% (53/100) Total lines: 7162/8/2026 - 12:14:11 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 7163/3/2026 - 12:13:24 AM Line coverage: 58.5% (154/263) Branch coverage: 53% (53/100) Total lines: 7163/4/2026 - 12:14:01 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 7163/28/2026 - 12:14:04 AM Line coverage: 58.5% (154/263) Branch coverage: 53% (53/100) Total lines: 7163/29/2026 - 12:13:48 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 7164/1/2026 - 12:13:37 AM Line coverage: 58.5% (154/263) Branch coverage: 53% (53/100) Total lines: 7164/2/2026 - 12:14:03 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 7164/12/2026 - 12:13:54 AM Line coverage: 58.9% (155/263) Branch coverage: 54% (54/100) Total lines: 7164/13/2026 - 12:14:08 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 7164/28/2026 - 12:13:10 AM Line coverage: 58.5% (154/263) Branch coverage: 53% (53/100) Total lines: 7164/29/2026 - 12:14:58 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 7165/4/2026 - 12:15:16 AM Line coverage: 56% (149/266) Branch coverage: 50.9% (52/102) Total lines: 722

Coverage delta

Coverage delta 3 -3

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        {
 4245            _logger = logger;
 4246            _tempPath = applicationPaths.TempDirectory;
 4247            _shortcutHandlers = shortcutHandlers.ToList();
 4248        }
 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        {
 14958            ArgumentException.ThrowIfNullOrEmpty(filename);
 59
 14960            var extension = Path.GetExtension(filename);
 14961            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
 91188            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            {
 91201                var fileInfo = new DirectoryInfo(path);
 202
 91203                if (fileInfo.Exists)
 204                {
 91205                    return GetFileSystemMetadata(fileInfo);
 206                }
 207
 0208                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        {
 129237            var fileInfo = new DirectoryInfo(path);
 238
 129239            return GetFileSystemMetadata(fileInfo);
 240        }
 241
 242        private FileSystemMetadata GetFileSystemMetadata(FileSystemInfo info)
 243        {
 402244            var result = new FileSystemMetadata
 402245            {
 402246                Exists = info.Exists,
 402247                FullName = info.FullName,
 402248                Extension = info.Extension,
 402249                Name = info.Name
 402250            };
 251
 402252            if (result.Exists)
 253            {
 402254                result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttrib
 255
 402256                if (info is FileInfo fileInfo)
 257                {
 48258                    result.CreationTimeUtc = GetCreationTimeUtc(info);
 48259                    result.LastWriteTimeUtc = GetLastWriteTimeUtc(info);
 48260                    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                    {
 47287                        result.Length = fileInfo.Length;
 288                    }
 289                }
 290            }
 291            else
 292            {
 0293                result.IsDirectory = info is DirectoryInfo;
 294            }
 295
 402296            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        {
 7307            var first = filename.IndexOfAny(_invalidPathCharacters);
 7308            if (first == -1)
 309            {
 310                // Fast path for clean strings
 4311                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            {
 48347                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            }
 48354        }
 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        {
 2371            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            {
 48384                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            }
 48391        }
 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        {
 0489            ArgumentException.ThrowIfNullOrEmpty(parentPath);
 0490            ArgumentException.ThrowIfNullOrEmpty(path);
 491
 0492            return path.Contains(
 0493                Path.TrimEndingDirectorySeparator(parentPath) + Path.DirectorySeparatorChar,
 0494                _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
 495        }
 496
 497        /// <inheritdoc />
 498        public virtual bool AreEqual(string path1, string path2)
 499        {
 175500            if (string.IsNullOrWhiteSpace(path1) || string.IsNullOrWhiteSpace(path2))
 501            {
 0502                return false;
 503            }
 504
 175505            var normalized1 = Path.TrimEndingDirectorySeparator(path1);
 175506            var normalized2 = Path.TrimEndingDirectorySeparator(path2);
 507
 175508            return string.Equals(
 175509                normalized1,
 175510                normalized2,
 175511                _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
 512        }
 513
 514        /// <inheritdoc />
 515        public virtual string GetFileNameWithoutExtension(FileSystemMetadata info)
 516        {
 0517            if (info.IsDirectory)
 518            {
 0519                return info.Name;
 520            }
 521
 0522            return Path.GetFileNameWithoutExtension(info.FullName);
 523        }
 524
 525        /// <inheritdoc />
 526        public virtual bool IsPathFile(string path)
 527        {
 2212528            if (path.Contains("://", StringComparison.OrdinalIgnoreCase)
 2212529                && !path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
 530            {
 0531                return false;
 532            }
 533
 2212534            return true;
 535        }
 536
 537        /// <inheritdoc />
 538        public virtual void DeleteFile(string path)
 539        {
 2540            SetAttributes(path, false, false);
 2541            File.Delete(path);
 2542        }
 543
 544        /// <inheritdoc />
 545        public virtual IEnumerable<FileSystemMetadata> GetDrives()
 546        {
 547            // check for ready state to avoid waiting for drives to timeout
 548            // some drives on linux have no actual size or are used for other purposes
 0549            return DriveInfo.GetDrives()
 0550                .Where(
 0551                    d => (d.DriveType == DriveType.Fixed || d.DriveType == DriveType.Network || d.DriveType == DriveType
 0552                         && d.IsReady
 0553                         && d.TotalSize != 0)
 0554                .Select(d => new FileSystemMetadata
 0555                {
 0556                    Name = d.Name,
 0557                    FullName = d.RootDirectory.FullName,
 0558                    IsDirectory = true
 0559                });
 560        }
 561
 562        /// <inheritdoc />
 563        public virtual IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
 564        {
 0565            return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", GetEnumerationOptions(recursive)));
 566        }
 567
 568        /// <inheritdoc />
 569        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false)
 570        {
 2571            return GetFiles(path, "*", recursive);
 572        }
 573
 574        /// <inheritdoc />
 575        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, string searchPattern, bool recursive = fals
 576        {
 2577            return GetFiles(path, searchPattern, null, false, recursive);
 578        }
 579
 580        /// <inheritdoc />
 581        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string>? extensions, bool ena
 582        {
 14583            return GetFiles(path, "*", extensions, enableCaseSensitiveExtensions, recursive);
 584        }
 585
 586        /// <inheritdoc />
 587        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, string searchPattern, IReadOnlyList<string>
 588        {
 16589            if (!Directory.Exists(path))
 590            {
 0591                _logger.LogWarning("Directory does not exist: {Path}", path);
 0592                return [];
 593            }
 594
 16595            var enumerationOptions = GetEnumerationOptions(recursive);
 596
 597            // On linux and macOS the search pattern is case-sensitive
 598            // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
 16599            if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions is not null && extensions
 600            {
 0601                searchPattern = searchPattern.EndsWith(extensions[0], StringComparison.Ordinal) ? searchPattern : search
 602
 0603                return ToMetadata(new DirectoryInfo(path).EnumerateFiles(searchPattern, enumerationOptions));
 604            }
 605
 16606            var files = new DirectoryInfo(path).EnumerateFiles(searchPattern, enumerationOptions);
 607
 16608            if (extensions is not null && extensions.Count > 0)
 609            {
 14610                files = files.Where(i =>
 14611                {
 14612                    var ext = i.Extension.AsSpan();
 14613                    if (ext.IsEmpty)
 14614                    {
 14615                        return false;
 14616                    }
 14617
 14618                    return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase);
 14619                });
 620            }
 621
 16622            return ToMetadata(files);
 623        }
 624
 625        /// <inheritdoc />
 626        public virtual IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
 627        {
 628            // Note: any of unhandled exceptions thrown by this method may cause the caller to believe the whole path is
 629            // But what causing the exception may be a single file under that path. This could lead to unexpected behavi
 630            // For example, the scanner will remove everything in that path due to unhandled errors.
 289631            var directoryInfo = new DirectoryInfo(path);
 289632            var enumerationOptions = GetEnumerationOptions(recursive);
 633
 289634            return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", enumerationOptions));
 635        }
 636
 637        private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos)
 638        {
 305639            return infos.Select(GetFileSystemMetadata);
 640        }
 641
 642        /// <inheritdoc />
 643        public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
 644        {
 26645            return Directory.EnumerateDirectories(path, "*", GetEnumerationOptions(recursive));
 646        }
 647
 648        /// <inheritdoc />
 649        public virtual IEnumerable<string> GetFilePaths(string path, bool recursive = false)
 650        {
 3651            return GetFilePaths(path, null, false, recursive);
 652        }
 653
 654        /// <inheritdoc />
 655        public virtual IEnumerable<string> GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExten
 656        {
 4657            var enumerationOptions = GetEnumerationOptions(recursive);
 658
 659            // On linux and macOS the search pattern is case-sensitive
 660            // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
 4661            if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions is not null && extensions
 662            {
 1663                return Directory.EnumerateFiles(path, "*" + extensions[0], enumerationOptions);
 664            }
 665
 3666            var files = Directory.EnumerateFiles(path, "*", enumerationOptions);
 667
 3668            if (extensions is not null && extensions.Length > 0)
 669            {
 0670                files = files.Where(i =>
 0671                {
 0672                    var ext = Path.GetExtension(i.AsSpan());
 0673                    if (ext.IsEmpty)
 0674                    {
 0675                        return false;
 0676                    }
 0677
 0678                    return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase);
 0679                });
 680            }
 681
 3682            return files;
 683        }
 684
 685        /// <inheritdoc />
 686        public virtual IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
 687        {
 688            try
 689            {
 38690                return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive));
 691            }
 0692            catch (Exception ex) when (ex is UnauthorizedAccessException or DirectoryNotFoundException or SecurityExcept
 693            {
 0694                _logger.LogError(ex, "Failed to enumerate path {Path}", path);
 0695                return Enumerable.Empty<string>();
 696            }
 38697        }
 698
 699        /// <inheritdoc />
 700        public virtual bool DirectoryExists(string path)
 701        {
 0702            return Directory.Exists(path);
 703        }
 704
 705        /// <inheritdoc />
 706        public virtual bool FileExists(string path)
 707        {
 0708            return File.Exists(path);
 709        }
 710
 711        private EnumerationOptions GetEnumerationOptions(bool recursive)
 712        {
 373713            return new EnumerationOptions
 373714            {
 373715                RecurseSubdirectories = recursive,
 373716                IgnoreInaccessible = true,
 373717                // Don't skip any files.
 373718                AttributesToSkip = 0
 373719            };
 720        }
 721    }
 722}

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