< 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: 150
Uncovered lines: 108
Coverable lines: 258
Total lines: 707
Line coverage: 58.1%
Branch coverage
53%
Covered branches: 51
Total branches: 96
Branch coverage: 53.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 8/19/2025 - 12:10:00 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7098/24/2025 - 12:11:19 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7098/25/2025 - 12:11:25 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7098/26/2025 - 12:09:43 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7098/27/2025 - 12:10:55 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7098/28/2025 - 12:10:57 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7098/29/2025 - 12:09:55 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7098/30/2025 - 12:09:58 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7099/2/2025 - 12:10:53 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7099/5/2025 - 12:11:19 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7099/7/2025 - 12:11:13 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: 707 8/19/2025 - 12:10:00 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7098/24/2025 - 12:11:19 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7098/25/2025 - 12:11:25 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7098/26/2025 - 12:09:43 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7098/27/2025 - 12:10:55 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7098/28/2025 - 12:10:57 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7098/29/2025 - 12:09:55 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7098/30/2025 - 12:09:58 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7099/2/2025 - 12:10:53 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7099/5/2025 - 12:11:19 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7099/7/2025 - 12:11:13 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: 707

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%22100%
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        {
 14458            ArgumentException.ThrowIfNullOrEmpty(filename);
 59
 14460            var extension = Path.GetExtension(filename);
 14461            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
 90188            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            {
 90201                var fileInfo = new DirectoryInfo(path);
 202
 90203                if (fileInfo.Exists)
 204                {
 90205                    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        {
 125237            var fileInfo = new DirectoryInfo(path);
 238
 125239            return GetFileSystemMetadata(fileInfo);
 240        }
 241
 242        private FileSystemMetadata GetFileSystemMetadata(FileSystemInfo info)
 243        {
 393244            var result = new FileSystemMetadata
 393245            {
 393246                Exists = info.Exists,
 393247                FullName = info.FullName,
 393248                Extension = info.Extension,
 393249                Name = info.Name
 393250            };
 251
 393252            if (result.Exists)
 253            {
 391254                result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttrib
 255
 391256                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
 393296            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            return Path.TrimEndingDirectorySeparator(path1).Equals(
 172501                Path.TrimEndingDirectorySeparator(path2),
 172502                _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
 503        }
 504
 505        /// <inheritdoc />
 506        public virtual string GetFileNameWithoutExtension(FileSystemMetadata info)
 507        {
 0508            if (info.IsDirectory)
 509            {
 0510                return info.Name;
 511            }
 512
 0513            return Path.GetFileNameWithoutExtension(info.FullName);
 514        }
 515
 516        /// <inheritdoc />
 517        public virtual bool IsPathFile(string path)
 518        {
 2085519            if (path.Contains("://", StringComparison.OrdinalIgnoreCase)
 2085520                && !path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
 521            {
 0522                return false;
 523            }
 524
 2085525            return true;
 526        }
 527
 528        /// <inheritdoc />
 529        public virtual void DeleteFile(string path)
 530        {
 2531            SetAttributes(path, false, false);
 2532            File.Delete(path);
 2533        }
 534
 535        /// <inheritdoc />
 536        public virtual IEnumerable<FileSystemMetadata> GetDrives()
 537        {
 538            // check for ready state to avoid waiting for drives to timeout
 539            // some drives on linux have no actual size or are used for other purposes
 0540            return DriveInfo.GetDrives()
 0541                .Where(
 0542                    d => (d.DriveType == DriveType.Fixed || d.DriveType == DriveType.Network || d.DriveType == DriveType
 0543                         && d.IsReady
 0544                         && d.TotalSize != 0)
 0545                .Select(d => new FileSystemMetadata
 0546                {
 0547                    Name = d.Name,
 0548                    FullName = d.RootDirectory.FullName,
 0549                    IsDirectory = true
 0550                });
 551        }
 552
 553        /// <inheritdoc />
 554        public virtual IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
 555        {
 0556            return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", GetEnumerationOptions(recursive)));
 557        }
 558
 559        /// <inheritdoc />
 560        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false)
 561        {
 2562            return GetFiles(path, "*", recursive);
 563        }
 564
 565        /// <inheritdoc />
 566        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, string searchPattern, bool recursive = fals
 567        {
 2568            return GetFiles(path, searchPattern, null, false, recursive);
 569        }
 570
 571        /// <inheritdoc />
 572        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string>? extensions, bool ena
 573        {
 11574            return GetFiles(path, "*", extensions, enableCaseSensitiveExtensions, recursive);
 575        }
 576
 577        /// <inheritdoc />
 578        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, string searchPattern, IReadOnlyList<string>
 579        {
 13580            var enumerationOptions = GetEnumerationOptions(recursive);
 581
 582            // On linux and macOS the search pattern is case-sensitive
 583            // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
 13584            if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions is not null && extensions
 585            {
 0586                searchPattern = searchPattern.EndsWith(extensions[0], StringComparison.Ordinal) ? searchPattern : search
 587
 0588                return ToMetadata(new DirectoryInfo(path).EnumerateFiles(searchPattern, enumerationOptions));
 589            }
 590
 13591            var files = new DirectoryInfo(path).EnumerateFiles(searchPattern, enumerationOptions);
 592
 13593            if (extensions is not null && extensions.Count > 0)
 594            {
 11595                files = files.Where(i =>
 11596                {
 11597                    var ext = i.Extension.AsSpan();
 11598                    if (ext.IsEmpty)
 11599                    {
 11600                        return false;
 11601                    }
 11602
 11603                    return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase);
 11604                });
 605            }
 606
 13607            return ToMetadata(files);
 608        }
 609
 610        /// <inheritdoc />
 611        public virtual IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
 612        {
 613            // Note: any of unhandled exceptions thrown by this method may cause the caller to believe the whole path is
 614            // But what causing the exception may be a single file under that path. This could lead to unexpected behavi
 615            // For example, the scanner will remove everything in that path due to unhandled errors.
 286616            var directoryInfo = new DirectoryInfo(path);
 286617            var enumerationOptions = GetEnumerationOptions(recursive);
 618
 286619            return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", enumerationOptions));
 620        }
 621
 622        private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos)
 623        {
 298624            return infos.Select(GetFileSystemMetadata);
 625        }
 626
 627        /// <inheritdoc />
 628        public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
 629        {
 26630            return Directory.EnumerateDirectories(path, "*", GetEnumerationOptions(recursive));
 631        }
 632
 633        /// <inheritdoc />
 634        public virtual IEnumerable<string> GetFilePaths(string path, bool recursive = false)
 635        {
 3636            return GetFilePaths(path, null, false, recursive);
 637        }
 638
 639        /// <inheritdoc />
 640        public virtual IEnumerable<string> GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExten
 641        {
 4642            var enumerationOptions = GetEnumerationOptions(recursive);
 643
 644            // On linux and macOS the search pattern is case-sensitive
 645            // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
 4646            if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions is not null && extensions
 647            {
 1648                return Directory.EnumerateFiles(path, "*" + extensions[0], enumerationOptions);
 649            }
 650
 3651            var files = Directory.EnumerateFiles(path, "*", enumerationOptions);
 652
 3653            if (extensions is not null && extensions.Length > 0)
 654            {
 0655                files = files.Where(i =>
 0656                {
 0657                    var ext = Path.GetExtension(i.AsSpan());
 0658                    if (ext.IsEmpty)
 0659                    {
 0660                        return false;
 0661                    }
 0662
 0663                    return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase);
 0664                });
 665            }
 666
 3667            return files;
 668        }
 669
 670        /// <inheritdoc />
 671        public virtual IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
 672        {
 673            try
 674            {
 38675                return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive));
 676            }
 0677            catch (Exception ex) when (ex is UnauthorizedAccessException or DirectoryNotFoundException or SecurityExcept
 678            {
 0679                _logger.LogError(ex, "Failed to enumerate path {Path}", path);
 0680                return Enumerable.Empty<string>();
 681            }
 38682        }
 683
 684        /// <inheritdoc />
 685        public virtual bool DirectoryExists(string path)
 686        {
 0687            return Directory.Exists(path);
 688        }
 689
 690        /// <inheritdoc />
 691        public virtual bool FileExists(string path)
 692        {
 0693            return File.Exists(path);
 694        }
 695
 696        private EnumerationOptions GetEnumerationOptions(bool recursive)
 697        {
 367698            return new EnumerationOptions
 367699            {
 367700                RecurseSubdirectories = recursive,
 367701                IgnoreInaccessible = true,
 367702                // Don't skip any files.
 367703                AttributesToSkip = 0
 367704            };
 705        }
 706    }
 707}

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)