< 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
58%
Covered lines: 154
Uncovered lines: 109
Coverable lines: 263
Total lines: 716
Line coverage: 58.5%
Branch coverage
53%
Covered branches: 53
Total branches: 100
Branch coverage: 53%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 9/14/2025 - 12:09:49 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7099/17/2025 - 12:11:23 AM Line coverage: 55.5% (144/259) Branch coverage: 54.4% (49/90) Total lines: 7099/20/2025 - 12:11:30 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7099/27/2025 - 12:11:20 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7099/28/2025 - 12:11:28 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 70910/27/2025 - 12:11:33 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 70910/28/2025 - 12:11:27 AM Line coverage: 58.1% (150/258) Branch coverage: 53.1% (51/96) Total lines: 70611/1/2025 - 12:11:13 AM Line coverage: 57.7% (149/258) Branch coverage: 52% (50/96) Total lines: 70611/2/2025 - 12:10:06 AM Line coverage: 58.1% (150/258) Branch coverage: 53.1% (51/96) Total lines: 70611/18/2025 - 12:11:25 AM Line coverage: 58.1% (150/258) Branch coverage: 53.1% (51/96) Total lines: 70711/26/2025 - 12:11:22 AM Line coverage: 57.7% (149/258) Branch coverage: 52% (50/96) Total lines: 70711/27/2025 - 12:10:09 AM Line coverage: 58.1% (150/258) Branch coverage: 53.1% (51/96) Total lines: 70711/30/2025 - 12:09:51 AM Line coverage: 57.7% (149/258) Branch coverage: 52% (50/96) Total lines: 70712/1/2025 - 12:11:46 AM Line coverage: 58.1% (150/258) Branch coverage: 53.1% (51/96) Total lines: 70712/4/2025 - 12:11:49 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 71612/5/2025 - 12:11:29 AM Line coverage: 58.5% (154/263) Branch coverage: 53% (53/100) Total lines: 716 9/14/2025 - 12:09:49 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7099/17/2025 - 12:11:23 AM Line coverage: 55.5% (144/259) Branch coverage: 54.4% (49/90) Total lines: 7099/20/2025 - 12:11:30 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7099/27/2025 - 12:11:20 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7099/28/2025 - 12:11:28 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 70910/27/2025 - 12:11:33 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 70910/28/2025 - 12:11:27 AM Line coverage: 58.1% (150/258) Branch coverage: 53.1% (51/96) Total lines: 70611/1/2025 - 12:11:13 AM Line coverage: 57.7% (149/258) Branch coverage: 52% (50/96) Total lines: 70611/2/2025 - 12:10:06 AM Line coverage: 58.1% (150/258) Branch coverage: 53.1% (51/96) Total lines: 70611/18/2025 - 12:11:25 AM Line coverage: 58.1% (150/258) Branch coverage: 53.1% (51/96) Total lines: 70711/26/2025 - 12:11:22 AM Line coverage: 57.7% (149/258) Branch coverage: 52% (50/96) Total lines: 70711/27/2025 - 12:10:09 AM Line coverage: 58.1% (150/258) Branch coverage: 53.1% (51/96) Total lines: 70711/30/2025 - 12:09:51 AM Line coverage: 57.7% (149/258) Branch coverage: 52% (50/96) Total lines: 70712/1/2025 - 12:11:46 AM Line coverage: 58.1% (150/258) Branch coverage: 53.1% (51/96) Total lines: 70712/4/2025 - 12:11:49 AM Line coverage: 58.1% (153/263) Branch coverage: 52% (52/100) Total lines: 71612/5/2025 - 12:11:29 AM Line coverage: 58.5% (154/263) Branch coverage: 53% (53/100) Total lines: 716

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
IsShortcut(...)100%11100%
ResolveShortcut(...)0%620%
MakeAbsolutePath(...)66.66%301250%
CreateShortcut(...)0%620%
MoveDirectory(...)75%44100%
GetFileSystemInfo(...)33.33%12644.44%
GetFileInfo(...)100%11100%
GetDirectoryInfo(...)100%11100%
GetFileSystemMetadata(...)58.33%171267.85%
GetValidFilename(...)100%22100%
GetCreationTimeUtc(...)100%1140%
GetCreationTimeUtc(...)100%210%
GetCreationTimeUtc(...)100%210%
GetLastWriteTimeUtc(...)100%11100%
GetLastWriteTimeUtc(...)100%1140%
GetLastWriteTimeUtc(...)100%210%
SetHidden(...)0%7280%
SetAttributes(...)8.33%1111211.76%
SwapFiles(...)100%210%
ContainsSubPath(...)50%22100%
AreEqual(...)50%6687.5%
GetFileNameWithoutExtension(...)0%620%
IsPathFile(...)75%4475%
DeleteFile(...)100%11100%
GetDrives()100%210%
GetDirectories(...)100%210%
GetFiles(...)100%11100%
GetFiles(...)100%11100%
GetFiles(...)100%11100%
GetFiles(...)78.57%141488.23%
GetFileSystemEntries(...)100%11100%
ToMetadata(...)100%11100%
GetDirectoryPaths(...)100%11100%
GetFilePaths(...)100%11100%
GetFilePaths(...)83.33%471237.5%
GetFileSystemEntryPaths(...)100%1140%
DirectoryExists(...)100%210%
FileExists(...)100%210%
GetEnumerationOptions(...)100%11100%

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        {
 14358            ArgumentException.ThrowIfNullOrEmpty(filename);
 59
 14360            var extension = Path.GetExtension(filename);
 14361            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
 88188            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            {
 88201                var fileInfo = new DirectoryInfo(path);
 202
 88203                if (fileInfo.Exists)
 204                {
 88205                    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        {
 124237            var fileInfo = new DirectoryInfo(path);
 238
 124239            return GetFileSystemMetadata(fileInfo);
 240        }
 241
 242        private FileSystemMetadata GetFileSystemMetadata(FileSystemInfo info)
 243        {
 388244            var result = new FileSystemMetadata
 388245            {
 388246                Exists = info.Exists,
 388247                FullName = info.FullName,
 388248                Extension = info.Extension,
 388249                Name = info.Name
 388250            };
 251
 388252            if (result.Exists)
 253            {
 386254                result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttrib
 255
 386256                if (info is FileInfo fileInfo)
 257                {
 43258                    result.CreationTimeUtc = GetCreationTimeUtc(info);
 43259                    result.LastWriteTimeUtc = GetLastWriteTimeUtc(info);
 43260                    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                    {
 42287                        result.Length = fileInfo.Length;
 288                    }
 289                }
 290            }
 291            else
 292            {
 2293                result.IsDirectory = info is DirectoryInfo;
 294            }
 295
 388296            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            {
 43347                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            }
 43354        }
 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            {
 43384                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            }
 43391        }
 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        {
 2489            ArgumentException.ThrowIfNullOrEmpty(parentPath);
 2490            ArgumentException.ThrowIfNullOrEmpty(path);
 491
 2492            return path.Contains(
 2493                Path.TrimEndingDirectorySeparator(parentPath) + Path.DirectorySeparatorChar,
 2494                _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
 495        }
 496
 497        /// <inheritdoc />
 498        public virtual bool AreEqual(string path1, string path2)
 499        {
 172500            if (string.IsNullOrWhiteSpace(path1) || string.IsNullOrWhiteSpace(path2))
 501            {
 0502                return false;
 503            }
 504
 172505            var normalized1 = Path.TrimEndingDirectorySeparator(path1);
 172506            var normalized2 = Path.TrimEndingDirectorySeparator(path2);
 507
 172508            return string.Equals(
 172509                normalized1,
 172510                normalized2,
 172511                _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        {
 2337528            if (path.Contains("://", StringComparison.OrdinalIgnoreCase)
 2337529                && !path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
 530            {
 0531                return false;
 532            }
 533
 2337534            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        {
 11583            return GetFiles(path, "*", extensions, enableCaseSensitiveExtensions, recursive);
 584        }
 585
 586        /// <inheritdoc />
 587        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, string searchPattern, IReadOnlyList<string>
 588        {
 13589            var enumerationOptions = GetEnumerationOptions(recursive);
 590
 591            // On linux and macOS the search pattern is case-sensitive
 592            // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
 13593            if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions is not null && extensions
 594            {
 0595                searchPattern = searchPattern.EndsWith(extensions[0], StringComparison.Ordinal) ? searchPattern : search
 596
 0597                return ToMetadata(new DirectoryInfo(path).EnumerateFiles(searchPattern, enumerationOptions));
 598            }
 599
 13600            var files = new DirectoryInfo(path).EnumerateFiles(searchPattern, enumerationOptions);
 601
 13602            if (extensions is not null && extensions.Count > 0)
 603            {
 11604                files = files.Where(i =>
 11605                {
 11606                    var ext = i.Extension.AsSpan();
 11607                    if (ext.IsEmpty)
 11608                    {
 11609                        return false;
 11610                    }
 11611
 11612                    return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase);
 11613                });
 614            }
 615
 13616            return ToMetadata(files);
 617        }
 618
 619        /// <inheritdoc />
 620        public virtual IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
 621        {
 622            // Note: any of unhandled exceptions thrown by this method may cause the caller to believe the whole path is
 623            // But what causing the exception may be a single file under that path. This could lead to unexpected behavi
 624            // For example, the scanner will remove everything in that path due to unhandled errors.
 282625            var directoryInfo = new DirectoryInfo(path);
 282626            var enumerationOptions = GetEnumerationOptions(recursive);
 627
 282628            return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", enumerationOptions));
 629        }
 630
 631        private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos)
 632        {
 294633            return infos.Select(GetFileSystemMetadata);
 634        }
 635
 636        /// <inheritdoc />
 637        public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
 638        {
 26639            return Directory.EnumerateDirectories(path, "*", GetEnumerationOptions(recursive));
 640        }
 641
 642        /// <inheritdoc />
 643        public virtual IEnumerable<string> GetFilePaths(string path, bool recursive = false)
 644        {
 3645            return GetFilePaths(path, null, false, recursive);
 646        }
 647
 648        /// <inheritdoc />
 649        public virtual IEnumerable<string> GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExten
 650        {
 4651            var enumerationOptions = GetEnumerationOptions(recursive);
 652
 653            // On linux and macOS the search pattern is case-sensitive
 654            // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
 4655            if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions is not null && extensions
 656            {
 1657                return Directory.EnumerateFiles(path, "*" + extensions[0], enumerationOptions);
 658            }
 659
 3660            var files = Directory.EnumerateFiles(path, "*", enumerationOptions);
 661
 3662            if (extensions is not null && extensions.Length > 0)
 663            {
 0664                files = files.Where(i =>
 0665                {
 0666                    var ext = Path.GetExtension(i.AsSpan());
 0667                    if (ext.IsEmpty)
 0668                    {
 0669                        return false;
 0670                    }
 0671
 0672                    return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase);
 0673                });
 674            }
 675
 3676            return files;
 677        }
 678
 679        /// <inheritdoc />
 680        public virtual IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
 681        {
 682            try
 683            {
 37684                return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive));
 685            }
 0686            catch (Exception ex) when (ex is UnauthorizedAccessException or DirectoryNotFoundException or SecurityExcept
 687            {
 0688                _logger.LogError(ex, "Failed to enumerate path {Path}", path);
 0689                return Enumerable.Empty<string>();
 690            }
 37691        }
 692
 693        /// <inheritdoc />
 694        public virtual bool DirectoryExists(string path)
 695        {
 0696            return Directory.Exists(path);
 697        }
 698
 699        /// <inheritdoc />
 700        public virtual bool FileExists(string path)
 701        {
 0702            return File.Exists(path);
 703        }
 704
 705        private EnumerationOptions GetEnumerationOptions(bool recursive)
 706        {
 362707            return new EnumerationOptions
 362708            {
 362709                RecurseSubdirectories = recursive,
 362710                IgnoreInaccessible = true,
 362711                // Don't skip any files.
 362712                AttributesToSkip = 0
 362713            };
 714        }
 715    }
 716}

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)