< 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: 706
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 7/22/2025 - 12:11:20 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7097/23/2025 - 12:11:37 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7097/25/2025 - 12:11:10 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7097/26/2025 - 12:11:20 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7097/27/2025 - 12:09:49 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7098/13/2025 - 12:11:40 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7098/14/2025 - 12:11:05 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: 706 7/22/2025 - 12:11:20 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7097/23/2025 - 12:11:37 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7097/25/2025 - 12:11:10 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7097/26/2025 - 12:11:20 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7097/27/2025 - 12:09:49 AM Line coverage: 57.5% (149/259) Branch coverage: 55.5% (50/90) Total lines: 7098/13/2025 - 12:11:40 AM Line coverage: 56.7% (147/259) Branch coverage: 53.3% (48/90) Total lines: 7098/14/2025 - 12:11:05 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: 706

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.Model.IO;
 10using Microsoft.Extensions.Logging;
 11
 12namespace Emby.Server.Implementations.IO
 13{
 14    /// <summary>
 15    /// Class ManagedFileSystem.
 16    /// </summary>
 17    public class ManagedFileSystem : IFileSystem
 18    {
 219        private static readonly bool _isEnvironmentCaseInsensitive = OperatingSystem.IsWindows();
 220        private static readonly char[] _invalidPathCharacters =
 221        {
 222            '\"', '<', '>', '|', '\0',
 223            (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
 224            (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
 225            (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
 226            (char)31, ':', '*', '?', '\\', '/'
 227        };
 28
 29        private readonly ILogger<ManagedFileSystem> _logger;
 30        private readonly List<IShortcutHandler> _shortcutHandlers;
 31        private readonly string _tempPath;
 32
 33        /// <summary>
 34        /// Initializes a new instance of the <see cref="ManagedFileSystem"/> class.
 35        /// </summary>
 36        /// <param name="logger">The <see cref="ILogger"/> instance to use.</param>
 37        /// <param name="applicationPaths">The <see cref="IApplicationPaths"/> instance to use.</param>
 38        /// <param name="shortcutHandlers">the <see cref="IShortcutHandler"/>'s to use.</param>
 39        public ManagedFileSystem(
 40            ILogger<ManagedFileSystem> logger,
 41            IApplicationPaths applicationPaths,
 42            IEnumerable<IShortcutHandler> shortcutHandlers)
 43        {
 4244            _logger = logger;
 4245            _tempPath = applicationPaths.TempDirectory;
 4246            _shortcutHandlers = shortcutHandlers.ToList();
 4247        }
 48
 49        /// <summary>
 50        /// Determines whether the specified filename is shortcut.
 51        /// </summary>
 52        /// <param name="filename">The filename.</param>
 53        /// <returns><c>true</c> if the specified filename is shortcut; otherwise, <c>false</c>.</returns>
 54        /// <exception cref="ArgumentNullException"><paramref name="filename"/> is <c>null</c>.</exception>
 55        public virtual bool IsShortcut(string filename)
 56        {
 14857            ArgumentException.ThrowIfNullOrEmpty(filename);
 58
 14859            var extension = Path.GetExtension(filename);
 14860            return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase))
 61        }
 62
 63        /// <summary>
 64        /// Resolves the shortcut.
 65        /// </summary>
 66        /// <param name="filename">The filename.</param>
 67        /// <returns>System.String.</returns>
 68        /// <exception cref="ArgumentNullException"><paramref name="filename"/> is <c>null</c>.</exception>
 69        public virtual string? ResolveShortcut(string filename)
 70        {
 071            ArgumentException.ThrowIfNullOrEmpty(filename);
 72
 073            var extension = Path.GetExtension(filename);
 074            var handler = _shortcutHandlers.Find(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgno
 75
 076            return handler?.Resolve(filename);
 77        }
 78
 79        /// <inheritdoc />
 80        public virtual string MakeAbsolutePath(string folderPath, string filePath)
 81        {
 82            // path is actually a stream
 483            if (string.IsNullOrWhiteSpace(filePath))
 84            {
 085                return filePath;
 86            }
 87
 488            var isAbsolutePath = Path.IsPathRooted(filePath) && (!OperatingSystem.IsWindows() || filePath[0] != '\\');
 89
 490            if (isAbsolutePath)
 91            {
 92                // absolute local path
 193                return filePath;
 94            }
 95
 96            // unc path
 397            if (filePath.StartsWith(@"\\", StringComparison.Ordinal))
 98            {
 099                return filePath;
 100            }
 101
 3102            var filePathSpan = filePath.AsSpan();
 103
 104            // relative path on windows
 3105            if (filePath[0] == '\\')
 106            {
 0107                filePathSpan = filePathSpan.Slice(1);
 108            }
 109
 110            try
 111            {
 3112                return Path.GetFullPath(Path.Join(folderPath, filePathSpan));
 113            }
 0114            catch (ArgumentException)
 115            {
 0116                return filePath;
 117            }
 0118            catch (PathTooLongException)
 119            {
 0120                return filePath;
 121            }
 0122            catch (NotSupportedException)
 123            {
 0124                return filePath;
 125            }
 3126        }
 127
 128        /// <summary>
 129        /// Creates the shortcut.
 130        /// </summary>
 131        /// <param name="shortcutPath">The shortcut path.</param>
 132        /// <param name="target">The target.</param>
 133        /// <exception cref="ArgumentNullException">The shortcutPath or target is null.</exception>
 134        public virtual void CreateShortcut(string shortcutPath, string target)
 135        {
 0136            ArgumentException.ThrowIfNullOrEmpty(shortcutPath);
 0137            ArgumentException.ThrowIfNullOrEmpty(target);
 138
 0139            var extension = Path.GetExtension(shortcutPath);
 0140            var handler = _shortcutHandlers.Find(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgno
 141
 0142            if (handler is not null)
 143            {
 0144                handler.Create(shortcutPath, target);
 145            }
 146            else
 147            {
 0148                throw new NotImplementedException();
 149            }
 150        }
 151
 152        /// <inheritdoc />
 153        public void MoveDirectory(string source, string destination)
 154        {
 155            // Make sure parent directory of target exists
 2156            var parent = Directory.GetParent(destination);
 2157            parent?.Create();
 158
 159            try
 160            {
 2161                Directory.Move(source, destination);
 1162            }
 1163            catch (IOException)
 164            {
 165                // Cross device move requires a copy
 1166                Directory.CreateDirectory(destination);
 1167                var sourceDir = new DirectoryInfo(source);
 6168                foreach (var file in sourceDir.EnumerateFiles())
 169                {
 2170                    file.CopyTo(Path.Combine(destination, file.Name), true);
 171                }
 172
 1173                sourceDir.Delete(true);
 1174            }
 2175        }
 176
 177        /// <summary>
 178        /// Returns a <see cref="FileSystemMetadata"/> object for the specified file or directory path.
 179        /// </summary>
 180        /// <param name="path">A path to a file or directory.</param>
 181        /// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
 182        /// <remarks>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> object's
 183        /// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and all other properties will refl
 184        public virtual FileSystemMetadata GetFileSystemInfo(string path)
 185        {
 186            // Take a guess to try and avoid two file system hits, but we'll double-check by calling Exists
 89187            if (Path.HasExtension(path))
 188            {
 0189                var fileInfo = new FileInfo(path);
 190
 0191                if (fileInfo.Exists)
 192                {
 0193                    return GetFileSystemMetadata(fileInfo);
 194                }
 195
 0196                return GetFileSystemMetadata(new DirectoryInfo(path));
 197            }
 198            else
 199            {
 89200                var fileInfo = new DirectoryInfo(path);
 201
 89202                if (fileInfo.Exists)
 203                {
 89204                    return GetFileSystemMetadata(fileInfo);
 205                }
 206
 0207                return GetFileSystemMetadata(new FileInfo(path));
 208            }
 209        }
 210
 211        /// <summary>
 212        /// Returns a <see cref="FileSystemMetadata"/> object for the specified file path.
 213        /// </summary>
 214        /// <param name="path">A path to a file.</param>
 215        /// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
 216        /// <remarks><para>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> ob
 217        /// <see cref="FileSystemMetadata.IsDirectory"/> property and the <see cref="FileSystemMetadata.Exists"/> proper
 218        /// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></r
 219        public virtual FileSystemMetadata GetFileInfo(string path)
 220        {
 1221            var fileInfo = new FileInfo(path);
 222
 1223            return GetFileSystemMetadata(fileInfo);
 224        }
 225
 226        /// <summary>
 227        /// Returns a <see cref="FileSystemMetadata"/> object for the specified directory path.
 228        /// </summary>
 229        /// <param name="path">A path to a directory.</param>
 230        /// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
 231        /// <remarks><para>If the specified path points to a file, the returned <see cref="FileSystemMetadata"/> object'
 232        /// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and the <see cref="FileSystemMetad
 233        /// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></r
 234        public virtual FileSystemMetadata GetDirectoryInfo(string path)
 235        {
 128236            var fileInfo = new DirectoryInfo(path);
 237
 128238            return GetFileSystemMetadata(fileInfo);
 239        }
 240
 241        private FileSystemMetadata GetFileSystemMetadata(FileSystemInfo info)
 242        {
 398243            var result = new FileSystemMetadata
 398244            {
 398245                Exists = info.Exists,
 398246                FullName = info.FullName,
 398247                Extension = info.Extension,
 398248                Name = info.Name
 398249            };
 250
 398251            if (result.Exists)
 252            {
 396253                result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttrib
 254
 396255                if (info is FileInfo fileInfo)
 256                {
 45257                    result.CreationTimeUtc = GetCreationTimeUtc(info);
 45258                    result.LastWriteTimeUtc = GetLastWriteTimeUtc(info);
 45259                    if (fileInfo.LinkTarget is not null)
 260                    {
 261                        try
 262                        {
 1263                            var targetFileInfo = (FileInfo?)fileInfo.ResolveLinkTarget(returnFinalTarget: true);
 1264                            if (targetFileInfo is not null)
 265                            {
 1266                                result.Exists = targetFileInfo.Exists;
 1267                                if (result.Exists)
 268                                {
 0269                                    result.Length = targetFileInfo.Length;
 0270                                    result.CreationTimeUtc = GetCreationTimeUtc(targetFileInfo);
 0271                                    result.LastWriteTimeUtc = GetLastWriteTimeUtc(targetFileInfo);
 272                                }
 273                            }
 274                            else
 275                            {
 0276                                result.Exists = false;
 277                            }
 1278                        }
 0279                        catch (UnauthorizedAccessException ex)
 280                        {
 0281                            _logger.LogError(ex, "Reading the file at {Path} failed due to a permissions exception.", fi
 0282                        }
 283                    }
 284                    else
 285                    {
 44286                        result.Length = fileInfo.Length;
 287                    }
 288                }
 289            }
 290            else
 291            {
 2292                result.IsDirectory = info is DirectoryInfo;
 293            }
 294
 398295            return result;
 296        }
 297
 298        /// <summary>
 299        /// Takes a filename and removes invalid characters.
 300        /// </summary>
 301        /// <param name="filename">The filename.</param>
 302        /// <returns>System.String.</returns>
 303        /// <exception cref="ArgumentNullException">The filename is null.</exception>
 304        public string GetValidFilename(string filename)
 305        {
 7306            var first = filename.IndexOfAny(_invalidPathCharacters);
 7307            if (first == -1)
 308            {
 309                // Fast path for clean strings
 4310                return filename;
 311            }
 312
 3313            return string.Create(
 3314                filename.Length,
 3315                (filename, _invalidPathCharacters, first),
 3316                (chars, state) =>
 3317                {
 3318                    state.filename.AsSpan().CopyTo(chars);
 3319
 3320                    chars[state.first++] = ' ';
 3321
 3322                    var len = chars.Length;
 3323                    foreach (var c in state._invalidPathCharacters)
 3324                    {
 3325                        for (int i = state.first; i < len; i++)
 3326                        {
 3327                            if (chars[i] == c)
 3328                            {
 3329                                chars[i] = ' ';
 3330                            }
 3331                        }
 3332                    }
 3333                });
 334        }
 335
 336        /// <summary>
 337        /// Gets the creation time UTC.
 338        /// </summary>
 339        /// <param name="info">The info.</param>
 340        /// <returns>DateTime.</returns>
 341        public DateTime GetCreationTimeUtc(FileSystemInfo info)
 342        {
 343            // This could throw an error on some file systems that have dates out of range
 344            try
 345            {
 45346                return info.CreationTimeUtc;
 347            }
 0348            catch (Exception ex)
 349            {
 0350                _logger.LogError(ex, "Error determining CreationTimeUtc for {FullName}", info.FullName);
 0351                return DateTime.MinValue;
 352            }
 45353        }
 354
 355        /// <inheritdoc />
 356        public virtual DateTime GetCreationTimeUtc(string path)
 357        {
 0358            return GetCreationTimeUtc(GetFileSystemInfo(path));
 359        }
 360
 361        /// <inheritdoc />
 362        public virtual DateTime GetCreationTimeUtc(FileSystemMetadata info)
 363        {
 0364            return info.CreationTimeUtc;
 365        }
 366
 367        /// <inheritdoc />
 368        public virtual DateTime GetLastWriteTimeUtc(FileSystemMetadata info)
 369        {
 2370            return info.LastWriteTimeUtc;
 371        }
 372
 373        /// <summary>
 374        /// Gets the creation time UTC.
 375        /// </summary>
 376        /// <param name="info">The info.</param>
 377        /// <returns>DateTime.</returns>
 378        public DateTime GetLastWriteTimeUtc(FileSystemInfo info)
 379        {
 380            // This could throw an error on some file systems that have dates out of range
 381            try
 382            {
 45383                return info.LastWriteTimeUtc;
 384            }
 0385            catch (Exception ex)
 386            {
 0387                _logger.LogError(ex, "Error determining LastAccessTimeUtc for {FullName}", info.FullName);
 0388                return DateTime.MinValue;
 389            }
 45390        }
 391
 392        /// <inheritdoc />
 393        public virtual DateTime GetLastWriteTimeUtc(string path)
 394        {
 0395            return GetLastWriteTimeUtc(GetFileSystemInfo(path));
 396        }
 397
 398        /// <inheritdoc />
 399        public virtual void SetHidden(string path, bool isHidden)
 400        {
 0401            if (!OperatingSystem.IsWindows())
 402            {
 0403                return;
 404            }
 405
 0406            var info = new FileInfo(path);
 407
 0408            if (info.Exists &&
 0409                (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden != isHidden)
 410            {
 0411                if (isHidden)
 412                {
 0413                    File.SetAttributes(path, info.Attributes | FileAttributes.Hidden);
 414                }
 415                else
 416                {
 0417                    File.SetAttributes(path, info.Attributes & ~FileAttributes.Hidden);
 418                }
 419            }
 0420        }
 421
 422        /// <inheritdoc />
 423        public virtual void SetAttributes(string path, bool isHidden, bool readOnly)
 424        {
 2425            if (!OperatingSystem.IsWindows())
 426            {
 2427                return;
 428            }
 429
 0430            var info = new FileInfo(path);
 431
 0432            if (!info.Exists)
 433            {
 0434                return;
 435            }
 436
 0437            if ((info.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly == readOnly
 0438                && (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden == isHidden)
 439            {
 0440                return;
 441            }
 442
 0443            var attributes = info.Attributes;
 444
 0445            if (readOnly)
 446            {
 0447                attributes |= FileAttributes.ReadOnly;
 448            }
 449            else
 450            {
 0451                attributes &= ~FileAttributes.ReadOnly;
 452            }
 453
 0454            if (isHidden)
 455            {
 0456                attributes |= FileAttributes.Hidden;
 457            }
 458            else
 459            {
 0460                attributes &= ~FileAttributes.Hidden;
 461            }
 462
 0463            File.SetAttributes(path, attributes);
 0464        }
 465
 466        /// <inheritdoc />
 467        public virtual void SwapFiles(string file1, string file2)
 468        {
 0469            ArgumentException.ThrowIfNullOrEmpty(file1);
 0470            ArgumentException.ThrowIfNullOrEmpty(file2);
 471
 0472            var temp1 = Path.Combine(_tempPath, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture));
 473
 474            // Copying over will fail against hidden files
 0475            SetHidden(file1, false);
 0476            SetHidden(file2, false);
 477
 0478            Directory.CreateDirectory(_tempPath);
 0479            File.Copy(file1, temp1, true);
 480
 0481            File.Copy(file2, file1, true);
 0482            File.Move(temp1, file2, true);
 0483        }
 484
 485        /// <inheritdoc />
 486        public virtual bool ContainsSubPath(string parentPath, string path)
 487        {
 1488            ArgumentException.ThrowIfNullOrEmpty(parentPath);
 1489            ArgumentException.ThrowIfNullOrEmpty(path);
 490
 1491            return path.Contains(
 1492                Path.TrimEndingDirectorySeparator(parentPath) + Path.DirectorySeparatorChar,
 1493                _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
 494        }
 495
 496        /// <inheritdoc />
 497        public virtual bool AreEqual(string path1, string path2)
 498        {
 176499            return Path.TrimEndingDirectorySeparator(path1).Equals(
 176500                Path.TrimEndingDirectorySeparator(path2),
 176501                _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
 502        }
 503
 504        /// <inheritdoc />
 505        public virtual string GetFileNameWithoutExtension(FileSystemMetadata info)
 506        {
 0507            if (info.IsDirectory)
 508            {
 0509                return info.Name;
 510            }
 511
 0512            return Path.GetFileNameWithoutExtension(info.FullName);
 513        }
 514
 515        /// <inheritdoc />
 516        public virtual bool IsPathFile(string path)
 517        {
 2417518            if (path.Contains("://", StringComparison.OrdinalIgnoreCase)
 2417519                && !path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
 520            {
 0521                return false;
 522            }
 523
 2417524            return true;
 525        }
 526
 527        /// <inheritdoc />
 528        public virtual void DeleteFile(string path)
 529        {
 2530            SetAttributes(path, false, false);
 2531            File.Delete(path);
 2532        }
 533
 534        /// <inheritdoc />
 535        public virtual IEnumerable<FileSystemMetadata> GetDrives()
 536        {
 537            // check for ready state to avoid waiting for drives to timeout
 538            // some drives on linux have no actual size or are used for other purposes
 0539            return DriveInfo.GetDrives()
 0540                .Where(
 0541                    d => (d.DriveType == DriveType.Fixed || d.DriveType == DriveType.Network || d.DriveType == DriveType
 0542                         && d.IsReady
 0543                         && d.TotalSize != 0)
 0544                .Select(d => new FileSystemMetadata
 0545                {
 0546                    Name = d.Name,
 0547                    FullName = d.RootDirectory.FullName,
 0548                    IsDirectory = true
 0549                });
 550        }
 551
 552        /// <inheritdoc />
 553        public virtual IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
 554        {
 0555            return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", GetEnumerationOptions(recursive)));
 556        }
 557
 558        /// <inheritdoc />
 559        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false)
 560        {
 2561            return GetFiles(path, "*", recursive);
 562        }
 563
 564        /// <inheritdoc />
 565        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, string searchPattern, bool recursive = fals
 566        {
 2567            return GetFiles(path, searchPattern, null, false, recursive);
 568        }
 569
 570        /// <inheritdoc />
 571        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string>? extensions, bool ena
 572        {
 12573            return GetFiles(path, "*", extensions, enableCaseSensitiveExtensions, recursive);
 574        }
 575
 576        /// <inheritdoc />
 577        public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, string searchPattern, IReadOnlyList<string>
 578        {
 14579            var enumerationOptions = GetEnumerationOptions(recursive);
 580
 581            // On linux and macOS the search pattern is case-sensitive
 582            // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
 14583            if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions is not null && extensions
 584            {
 0585                searchPattern = searchPattern.EndsWith(extensions[0], StringComparison.Ordinal) ? searchPattern : search
 586
 0587                return ToMetadata(new DirectoryInfo(path).EnumerateFiles(searchPattern, enumerationOptions));
 588            }
 589
 14590            var files = new DirectoryInfo(path).EnumerateFiles(searchPattern, enumerationOptions);
 591
 14592            if (extensions is not null && extensions.Count > 0)
 593            {
 12594                files = files.Where(i =>
 12595                {
 12596                    var ext = i.Extension.AsSpan();
 12597                    if (ext.IsEmpty)
 12598                    {
 12599                        return false;
 12600                    }
 12601
 12602                    return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase);
 12603                });
 604            }
 605
 14606            return ToMetadata(files);
 607        }
 608
 609        /// <inheritdoc />
 610        public virtual IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
 611        {
 612            // Note: any of unhandled exceptions thrown by this method may cause the caller to believe the whole path is
 613            // But what causing the exception may be a single file under that path. This could lead to unexpected behavi
 614            // For example, the scanner will remove everything in that path due to unhandled errors.
 290615            var directoryInfo = new DirectoryInfo(path);
 290616            var enumerationOptions = GetEnumerationOptions(recursive);
 617
 290618            return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", enumerationOptions));
 619        }
 620
 621        private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos)
 622        {
 303623            return infos.Select(GetFileSystemMetadata);
 624        }
 625
 626        /// <inheritdoc />
 627        public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
 628        {
 26629            return Directory.EnumerateDirectories(path, "*", GetEnumerationOptions(recursive));
 630        }
 631
 632        /// <inheritdoc />
 633        public virtual IEnumerable<string> GetFilePaths(string path, bool recursive = false)
 634        {
 3635            return GetFilePaths(path, null, false, recursive);
 636        }
 637
 638        /// <inheritdoc />
 639        public virtual IEnumerable<string> GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExten
 640        {
 4641            var enumerationOptions = GetEnumerationOptions(recursive);
 642
 643            // On linux and macOS the search pattern is case-sensitive
 644            // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
 4645            if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions is not null && extensions
 646            {
 1647                return Directory.EnumerateFiles(path, "*" + extensions[0], enumerationOptions);
 648            }
 649
 3650            var files = Directory.EnumerateFiles(path, "*", enumerationOptions);
 651
 3652            if (extensions is not null && extensions.Length > 0)
 653            {
 0654                files = files.Where(i =>
 0655                {
 0656                    var ext = Path.GetExtension(i.AsSpan());
 0657                    if (ext.IsEmpty)
 0658                    {
 0659                        return false;
 0660                    }
 0661
 0662                    return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase);
 0663                });
 664            }
 665
 3666            return files;
 667        }
 668
 669        /// <inheritdoc />
 670        public virtual IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
 671        {
 672            try
 673            {
 38674                return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive));
 675            }
 0676            catch (Exception ex) when (ex is UnauthorizedAccessException or DirectoryNotFoundException or SecurityExcept
 677            {
 0678                _logger.LogError(ex, "Failed to enumerate path {Path}", path);
 0679                return Enumerable.Empty<string>();
 680            }
 38681        }
 682
 683        /// <inheritdoc />
 684        public virtual bool DirectoryExists(string path)
 685        {
 0686            return Directory.Exists(path);
 687        }
 688
 689        /// <inheritdoc />
 690        public virtual bool FileExists(string path)
 691        {
 0692            return File.Exists(path);
 693        }
 694
 695        private EnumerationOptions GetEnumerationOptions(bool recursive)
 696        {
 372697            return new EnumerationOptions
 372698            {
 372699                RecurseSubdirectories = recursive,
 372700                IgnoreInaccessible = true,
 372701                // Don't skip any files.
 372702                AttributesToSkip = 0
 372703            };
 704        }
 705    }
 706}

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)