2019-01-06 20:50:43 +00:00
using System ;
2016-10-29 05:40:15 +00:00
using System.Collections.Generic ;
2020-01-13 06:25:54 +00:00
using System.Globalization ;
2016-10-29 05:40:15 +00:00
using System.IO ;
using System.Linq ;
2021-06-19 16:02:33 +00:00
using Jellyfin.Extensions ;
2019-02-08 09:13:58 +00:00
using MediaBrowser.Common.Configuration ;
2016-10-29 05:40:15 +00:00
using MediaBrowser.Model.IO ;
2019-01-13 19:21:32 +00:00
using Microsoft.Extensions.Logging ;
2016-10-29 05:40:15 +00:00
2017-08-16 06:43:41 +00:00
namespace Emby.Server.Implementations.IO
2016-10-29 05:40:15 +00:00
{
/// <summary>
2020-01-13 06:25:54 +00:00
/// Class ManagedFileSystem.
2016-10-29 05:40:15 +00:00
/// </summary>
public class ManagedFileSystem : IFileSystem
{
2021-10-02 17:31:31 +00:00
private readonly ILogger < ManagedFileSystem > _logger ;
2016-10-29 05:40:15 +00:00
private readonly List < IShortcutHandler > _shortcutHandlers = new List < IShortcutHandler > ( ) ;
2019-02-08 09:13:58 +00:00
private readonly string _tempPath ;
2021-07-12 18:20:50 +00:00
private static readonly bool _isEnvironmentCaseInsensitive = OperatingSystem . IsWindows ( ) ;
2017-04-02 00:36:06 +00:00
2021-11-07 21:32:08 +00:00
/// <summary>
/// Initializes a new instance of the <see cref="ManagedFileSystem"/> class.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> instance to use.</param>
/// <param name="applicationPaths">The <see cref="IApplicationPaths"/> instance to use.</param>
2019-01-17 22:55:05 +00:00
public ManagedFileSystem (
2019-06-09 21:51:52 +00:00
ILogger < ManagedFileSystem > logger ,
2019-02-17 10:54:47 +00:00
IApplicationPaths applicationPaths )
2016-10-29 05:40:15 +00:00
{
2021-10-02 17:31:31 +00:00
_logger = logger ;
2019-02-08 09:13:58 +00:00
_tempPath = applicationPaths . TempDirectory ;
2018-09-12 17:26:21 +00:00
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual void AddShortcutHandler ( IShortcutHandler handler )
2016-10-29 05:40:15 +00:00
{
_shortcutHandlers . Add ( handler ) ;
}
/// <summary>
/// Determines whether the specified filename is shortcut.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns><c>true</c> if the specified filename is shortcut; otherwise, <c>false</c>.</returns>
2021-10-02 17:31:31 +00:00
/// <exception cref="ArgumentNullException"><paramref name="filename"/> is <c>null</c>.</exception>
2016-10-29 05:40:15 +00:00
public virtual bool IsShortcut ( string filename )
{
if ( string . IsNullOrEmpty ( filename ) )
{
2019-01-06 20:50:43 +00:00
throw new ArgumentNullException ( nameof ( filename ) ) ;
2016-10-29 05:40:15 +00:00
}
var extension = Path . GetExtension ( filename ) ;
2021-04-22 01:11:21 +00:00
return _shortcutHandlers . Any ( i = > string . Equals ( extension , i . Extension , StringComparison . OrdinalIgnoreCase ) ) ;
2016-10-29 05:40:15 +00:00
}
/// <summary>
/// Resolves the shortcut.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>System.String.</returns>
2021-10-02 17:31:31 +00:00
/// <exception cref="ArgumentNullException"><paramref name="filename"/> is <c>null</c>.</exception>
2021-05-20 19:28:18 +00:00
public virtual string? ResolveShortcut ( string filename )
2016-10-29 05:40:15 +00:00
{
if ( string . IsNullOrEmpty ( filename ) )
{
2019-01-06 20:50:43 +00:00
throw new ArgumentNullException ( nameof ( filename ) ) ;
2016-10-29 05:40:15 +00:00
}
var extension = Path . GetExtension ( filename ) ;
2021-04-22 01:11:21 +00:00
var handler = _shortcutHandlers . Find ( i = > string . Equals ( extension , i . Extension , StringComparison . OrdinalIgnoreCase ) ) ;
2016-10-29 05:40:15 +00:00
2019-03-28 22:19:56 +00:00
return handler ? . Resolve ( filename ) ;
2016-10-29 05:40:15 +00:00
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual string MakeAbsolutePath ( string folderPath , string filePath )
2018-09-12 17:26:21 +00:00
{
2020-01-13 06:25:54 +00:00
// path is actually a stream
2020-01-13 08:09:22 +00:00
if ( string . IsNullOrWhiteSpace ( filePath ) | | filePath . Contains ( "://" , StringComparison . Ordinal ) )
2019-03-28 22:19:56 +00:00
{
return filePath ;
}
if ( filePath . Length > 3 & & filePath [ 1 ] = = ':' & & filePath [ 2 ] = = '/' )
{
2020-01-13 06:25:54 +00:00
// absolute local path
return filePath ;
2019-03-28 22:19:56 +00:00
}
2018-09-12 17:26:21 +00:00
// unc path
2020-01-13 08:09:22 +00:00
if ( filePath . StartsWith ( "\\\\" , StringComparison . Ordinal ) )
2018-09-12 17:26:21 +00:00
{
return filePath ;
}
var firstChar = filePath [ 0 ] ;
if ( firstChar = = '/' )
{
2020-01-13 06:25:54 +00:00
// for this we don't really know
2018-09-12 17:26:21 +00:00
return filePath ;
}
2020-01-13 06:25:54 +00:00
// relative path
if ( firstChar = = '\\' )
2018-09-12 17:26:21 +00:00
{
filePath = filePath . Substring ( 1 ) ;
}
2020-01-13 06:25:54 +00:00
2018-09-12 17:26:21 +00:00
try
{
2020-01-08 09:05:07 +00:00
return Path . GetFullPath ( Path . Combine ( folderPath , filePath ) ) ;
2018-09-12 17:26:21 +00:00
}
2018-12-20 12:11:26 +00:00
catch ( ArgumentException )
2018-09-12 17:26:21 +00:00
{
return filePath ;
}
catch ( PathTooLongException )
{
return filePath ;
}
catch ( NotSupportedException )
{
return filePath ;
}
}
2016-10-29 05:40:15 +00:00
/// <summary>
/// Creates the shortcut.
/// </summary>
/// <param name="shortcutPath">The shortcut path.</param>
/// <param name="target">The target.</param>
2020-01-13 06:25:54 +00:00
/// <exception cref="ArgumentNullException">The shortcutPath or target is null.</exception>
2019-01-26 22:24:28 +00:00
public virtual void CreateShortcut ( string shortcutPath , string target )
2016-10-29 05:40:15 +00:00
{
if ( string . IsNullOrEmpty ( shortcutPath ) )
{
2019-01-06 20:50:43 +00:00
throw new ArgumentNullException ( nameof ( shortcutPath ) ) ;
2016-10-29 05:40:15 +00:00
}
if ( string . IsNullOrEmpty ( target ) )
{
2019-01-06 20:50:43 +00:00
throw new ArgumentNullException ( nameof ( target ) ) ;
2016-10-29 05:40:15 +00:00
}
var extension = Path . GetExtension ( shortcutPath ) ;
2019-03-28 22:19:56 +00:00
var handler = _shortcutHandlers . Find ( i = > string . Equals ( extension , i . Extension , StringComparison . OrdinalIgnoreCase ) ) ;
2016-10-29 05:40:15 +00:00
if ( handler ! = null )
{
handler . Create ( shortcutPath , target ) ;
}
else
{
throw new NotImplementedException ( ) ;
}
}
/// <summary>
/// Returns a <see cref="FileSystemMetadata"/> object for the specified file or directory path.
/// </summary>
/// <param name="path">A path to a file or directory.</param>
/// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
/// <remarks>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> object's
/// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and all other properties will reflect the properties of the directory.</remarks>
2019-01-26 22:24:28 +00:00
public virtual FileSystemMetadata GetFileSystemInfo ( string path )
2016-10-29 05:40:15 +00:00
{
// Take a guess to try and avoid two file system hits, but we'll double-check by calling Exists
if ( Path . HasExtension ( path ) )
{
var fileInfo = new FileInfo ( path ) ;
if ( fileInfo . Exists )
{
return GetFileSystemMetadata ( fileInfo ) ;
}
return GetFileSystemMetadata ( new DirectoryInfo ( path ) ) ;
}
else
{
var fileInfo = new DirectoryInfo ( path ) ;
if ( fileInfo . Exists )
{
return GetFileSystemMetadata ( fileInfo ) ;
}
return GetFileSystemMetadata ( new FileInfo ( path ) ) ;
}
}
/// <summary>
/// Returns a <see cref="FileSystemMetadata"/> object for the specified file path.
/// </summary>
/// <param name="path">A path to a file.</param>
/// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
/// <remarks><para>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> object's
/// <see cref="FileSystemMetadata.IsDirectory"/> property and the <see cref="FileSystemMetadata.Exists"/> property will both be set to false.</para>
/// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></remarks>
2019-01-26 22:24:28 +00:00
public virtual FileSystemMetadata GetFileInfo ( string path )
2016-10-29 05:40:15 +00:00
{
var fileInfo = new FileInfo ( path ) ;
return GetFileSystemMetadata ( fileInfo ) ;
}
/// <summary>
/// Returns a <see cref="FileSystemMetadata"/> object for the specified directory path.
/// </summary>
/// <param name="path">A path to a directory.</param>
/// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
/// <remarks><para>If the specified path points to a file, the returned <see cref="FileSystemMetadata"/> object's
/// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and the <see cref="FileSystemMetadata.Exists"/> property will be set to false.</para>
/// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></remarks>
2019-01-26 22:24:28 +00:00
public virtual FileSystemMetadata GetDirectoryInfo ( string path )
2016-10-29 05:40:15 +00:00
{
var fileInfo = new DirectoryInfo ( path ) ;
return GetFileSystemMetadata ( fileInfo ) ;
}
private FileSystemMetadata GetFileSystemMetadata ( FileSystemInfo info )
{
2019-03-28 22:19:56 +00:00
var result = new FileSystemMetadata
{
Exists = info . Exists ,
FullName = info . FullName ,
Extension = info . Extension ,
Name = info . Name
} ;
2016-10-29 05:40:15 +00:00
if ( result . Exists )
{
2018-09-12 17:26:21 +00:00
result . IsDirectory = info is DirectoryInfo | | ( info . Attributes & FileAttributes . Directory ) = = FileAttributes . Directory ;
2020-06-14 09:11:11 +00:00
// if (!result.IsDirectory)
2021-10-02 17:31:31 +00:00
// {
2018-09-12 17:26:21 +00:00
// result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
2021-10-02 17:31:31 +00:00
// }
2016-10-29 05:40:15 +00:00
2019-03-28 22:19:56 +00:00
if ( info is FileInfo fileInfo )
2016-10-29 05:40:15 +00:00
{
result . Length = fileInfo . Length ;
2020-05-31 22:40:02 +00:00
2021-05-23 22:30:41 +00:00
// Issue #2354 get the size of files behind symbolic links. Also Enum.HasFlag is bad as it boxes!
if ( ( fileInfo . Attributes & FileAttributes . ReparsePoint ) = = FileAttributes . ReparsePoint )
2020-05-31 22:40:02 +00:00
{
2021-04-09 21:02:36 +00:00
try
2020-05-31 22:40:02 +00:00
{
2021-09-25 18:17:12 +00:00
using ( var fileHandle = File . OpenHandle ( fileInfo . FullName , FileMode . Open , FileAccess . Read , FileShare . ReadWrite ) )
2021-04-09 21:02:36 +00:00
{
2021-09-25 18:17:12 +00:00
result . Length = RandomAccess . GetLength ( fileHandle ) ;
2021-04-09 21:02:36 +00:00
}
}
catch ( FileNotFoundException ex )
{
// Dangling symlinks cannot be detected before opening the file unfortunately...
2021-10-02 17:31:31 +00:00
_logger . LogError ( ex , "Reading the file size of the symlink at {Path} failed. Marking the file as not existing." , fileInfo . FullName ) ;
2021-04-09 21:02:36 +00:00
result . Exists = false ;
2020-05-31 22:40:02 +00:00
}
2022-05-16 00:26:00 +00:00
catch ( UnauthorizedAccessException ex )
{
_logger . LogError ( ex , "Reading the file at {Path} failed due to a permissions exception." , fileInfo . FullName ) ;
}
2020-05-31 22:40:02 +00:00
}
2016-10-29 05:40:15 +00:00
}
result . CreationTimeUtc = GetCreationTimeUtc ( info ) ;
result . LastWriteTimeUtc = GetLastWriteTimeUtc ( info ) ;
}
else
{
result . IsDirectory = info is DirectoryInfo ;
}
return result ;
}
2019-01-06 20:50:43 +00:00
private static ExtendedFileSystemInfo GetExtendedFileSystemInfo ( string path )
2018-09-12 17:26:21 +00:00
{
var result = new ExtendedFileSystemInfo ( ) ;
var info = new FileInfo ( path ) ;
if ( info . Exists )
{
result . Exists = true ;
var attributes = info . Attributes ;
result . IsHidden = ( attributes & FileAttributes . Hidden ) = = FileAttributes . Hidden ;
result . IsReadOnly = ( attributes & FileAttributes . ReadOnly ) = = FileAttributes . ReadOnly ;
}
return result ;
}
2016-10-29 05:40:15 +00:00
/// <summary>
2020-01-13 06:25:54 +00:00
/// Takes a filename and removes invalid characters.
2016-10-29 05:40:15 +00:00
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>System.String.</returns>
2020-01-13 06:25:54 +00:00
/// <exception cref="ArgumentNullException">The filename is null.</exception>
2021-04-22 01:11:21 +00:00
public string GetValidFilename ( string filename )
2016-10-29 05:40:15 +00:00
{
2021-04-22 10:31:47 +00:00
var invalid = Path . GetInvalidFileNameChars ( ) ;
var first = filename . IndexOfAny ( invalid ) ;
if ( first = = - 1 )
{
// Fast path for clean strings
return filename ;
}
2021-04-22 01:11:21 +00:00
return string . Create (
filename . Length ,
2021-04-22 10:31:47 +00:00
( filename , invalid , first ) ,
2021-04-22 01:11:21 +00:00
( chars , state ) = >
{
2021-04-22 10:31:47 +00:00
state . filename . AsSpan ( ) . CopyTo ( chars ) ;
2021-04-22 01:11:21 +00:00
2021-04-22 10:31:47 +00:00
chars [ state . first + + ] = ' ' ;
2021-04-22 01:11:21 +00:00
var len = chars . Length ;
2021-04-22 10:31:47 +00:00
foreach ( var c in state . invalid )
2021-04-22 01:11:21 +00:00
{
2021-04-22 10:31:47 +00:00
for ( int i = state . first ; i < len ; i + + )
2021-04-22 01:11:21 +00:00
{
if ( chars [ i ] = = c )
{
chars [ i ] = ' ' ;
}
}
}
} ) ;
2016-10-29 05:40:15 +00:00
}
/// <summary>
/// Gets the creation time UTC.
/// </summary>
/// <param name="info">The info.</param>
/// <returns>DateTime.</returns>
public DateTime GetCreationTimeUtc ( FileSystemInfo info )
{
// This could throw an error on some file systems that have dates out of range
try
{
return info . CreationTimeUtc ;
}
catch ( Exception ex )
{
2021-10-02 17:31:31 +00:00
_logger . LogError ( ex , "Error determining CreationTimeUtc for {FullName}" , info . FullName ) ;
2016-10-29 05:40:15 +00:00
return DateTime . MinValue ;
}
}
/// <summary>
/// Gets the creation time UTC.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>DateTime.</returns>
2019-01-26 22:24:28 +00:00
public virtual DateTime GetCreationTimeUtc ( string path )
2016-10-29 05:40:15 +00:00
{
return GetCreationTimeUtc ( GetFileSystemInfo ( path ) ) ;
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual DateTime GetCreationTimeUtc ( FileSystemMetadata info )
2016-10-29 05:40:15 +00:00
{
return info . CreationTimeUtc ;
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual DateTime GetLastWriteTimeUtc ( FileSystemMetadata info )
2016-10-29 05:40:15 +00:00
{
return info . LastWriteTimeUtc ;
}
/// <summary>
/// Gets the creation time UTC.
/// </summary>
/// <param name="info">The info.</param>
/// <returns>DateTime.</returns>
public DateTime GetLastWriteTimeUtc ( FileSystemInfo info )
{
// This could throw an error on some file systems that have dates out of range
try
{
return info . LastWriteTimeUtc ;
}
catch ( Exception ex )
{
2021-10-02 17:31:31 +00:00
_logger . LogError ( ex , "Error determining LastAccessTimeUtc for {FullName}" , info . FullName ) ;
2016-10-29 05:40:15 +00:00
return DateTime . MinValue ;
}
}
/// <summary>
/// Gets the last write time UTC.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>DateTime.</returns>
2019-01-26 22:24:28 +00:00
public virtual DateTime GetLastWriteTimeUtc ( string path )
2016-10-29 05:40:15 +00:00
{
return GetLastWriteTimeUtc ( GetFileSystemInfo ( path ) ) ;
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual void SetHidden ( string path , bool isHidden )
2016-10-29 05:40:15 +00:00
{
2021-07-12 18:20:50 +00:00
if ( ! OperatingSystem . IsWindows ( ) )
2017-11-26 04:48:12 +00:00
{
return ;
}
2018-09-12 17:26:21 +00:00
var info = GetExtendedFileSystemInfo ( path ) ;
2016-10-29 05:40:15 +00:00
if ( info . Exists & & info . IsHidden ! = isHidden )
{
if ( isHidden )
{
2016-10-31 04:28:23 +00:00
File . SetAttributes ( path , File . GetAttributes ( path ) | FileAttributes . Hidden ) ;
2016-10-29 05:40:15 +00:00
}
else
{
2019-01-13 20:37:13 +00:00
var attributes = File . GetAttributes ( path ) ;
2016-10-31 04:28:23 +00:00
attributes = RemoveAttribute ( attributes , FileAttributes . Hidden ) ;
File . SetAttributes ( path , attributes ) ;
2016-10-29 05:40:15 +00:00
}
}
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2021-09-03 16:46:34 +00:00
public virtual void SetAttributes ( string path , bool isHidden , bool readOnly )
2017-05-12 04:54:19 +00:00
{
2021-07-12 18:20:50 +00:00
if ( ! OperatingSystem . IsWindows ( ) )
2017-11-26 04:48:12 +00:00
{
return ;
}
2018-09-12 17:26:21 +00:00
var info = GetExtendedFileSystemInfo ( path ) ;
2017-05-12 04:54:19 +00:00
if ( ! info . Exists )
{
return ;
}
2021-09-03 16:46:34 +00:00
if ( info . IsReadOnly = = readOnly & & info . IsHidden = = isHidden )
2017-05-12 04:54:19 +00:00
{
return ;
}
var attributes = File . GetAttributes ( path ) ;
2021-09-03 16:46:34 +00:00
if ( readOnly )
2017-05-12 04:54:19 +00:00
{
2021-11-07 21:32:08 +00:00
attributes | = FileAttributes . ReadOnly ;
2017-05-12 04:54:19 +00:00
}
else
{
attributes = RemoveAttribute ( attributes , FileAttributes . ReadOnly ) ;
}
if ( isHidden )
{
2021-11-07 21:32:08 +00:00
attributes | = FileAttributes . Hidden ;
2017-05-12 04:54:19 +00:00
}
else
{
attributes = RemoveAttribute ( attributes , FileAttributes . Hidden ) ;
}
File . SetAttributes ( path , attributes ) ;
}
2016-10-29 05:40:15 +00:00
private static FileAttributes RemoveAttribute ( FileAttributes attributes , FileAttributes attributesToRemove )
{
return attributes & ~ attributesToRemove ;
}
/// <summary>
/// Swaps the files.
/// </summary>
/// <param name="file1">The file1.</param>
/// <param name="file2">The file2.</param>
2019-01-26 22:24:28 +00:00
public virtual void SwapFiles ( string file1 , string file2 )
2016-10-29 05:40:15 +00:00
{
if ( string . IsNullOrEmpty ( file1 ) )
{
2019-01-06 20:50:43 +00:00
throw new ArgumentNullException ( nameof ( file1 ) ) ;
2016-10-29 05:40:15 +00:00
}
if ( string . IsNullOrEmpty ( file2 ) )
{
2019-01-06 20:50:43 +00:00
throw new ArgumentNullException ( nameof ( file2 ) ) ;
2016-10-29 05:40:15 +00:00
}
2019-02-28 22:22:57 +00:00
var temp1 = Path . Combine ( _tempPath , Guid . NewGuid ( ) . ToString ( "N" , CultureInfo . InvariantCulture ) ) ;
2016-10-29 05:40:15 +00:00
// Copying over will fail against hidden files
2016-12-27 07:25:51 +00:00
SetHidden ( file1 , false ) ;
SetHidden ( file2 , false ) ;
2016-10-29 05:40:15 +00:00
2017-01-23 21:51:23 +00:00
Directory . CreateDirectory ( _tempPath ) ;
2019-01-26 22:24:28 +00:00
File . Copy ( file1 , temp1 , true ) ;
2016-10-29 05:40:15 +00:00
2019-01-26 22:24:28 +00:00
File . Copy ( file2 , file1 , true ) ;
File . Copy ( temp1 , file2 , true ) ;
2016-10-29 05:40:15 +00:00
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual bool ContainsSubPath ( string parentPath , string path )
2016-10-29 05:40:15 +00:00
{
if ( string . IsNullOrEmpty ( parentPath ) )
{
2019-01-06 20:50:43 +00:00
throw new ArgumentNullException ( nameof ( parentPath ) ) ;
2016-10-29 05:40:15 +00:00
}
if ( string . IsNullOrEmpty ( path ) )
{
2019-01-06 20:50:43 +00:00
throw new ArgumentNullException ( nameof ( path ) ) ;
2016-10-29 05:40:15 +00:00
}
2021-04-01 17:39:00 +00:00
return path . Contains (
Path . TrimEndingDirectorySeparator ( parentPath ) + Path . DirectorySeparatorChar ,
_isEnvironmentCaseInsensitive ? StringComparison . OrdinalIgnoreCase : StringComparison . Ordinal ) ;
2016-10-29 05:40:15 +00:00
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual string NormalizePath ( string path )
2016-10-29 05:40:15 +00:00
{
if ( string . IsNullOrEmpty ( path ) )
{
2019-01-06 20:50:43 +00:00
throw new ArgumentNullException ( nameof ( path ) ) ;
2016-10-29 05:40:15 +00:00
}
if ( path . EndsWith ( ":\\" , StringComparison . OrdinalIgnoreCase ) )
{
return path ;
}
2021-04-01 17:39:00 +00:00
return Path . TrimEndingDirectorySeparator ( path ) ;
2016-10-29 05:40:15 +00:00
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual bool AreEqual ( string path1 , string path2 )
2017-05-04 18:14:45 +00:00
{
2021-04-01 17:39:00 +00:00
return string . Equals (
NormalizePath ( path1 ) ,
NormalizePath ( path2 ) ,
_isEnvironmentCaseInsensitive ? StringComparison . OrdinalIgnoreCase : StringComparison . Ordinal ) ;
2017-05-04 18:14:45 +00:00
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual string GetFileNameWithoutExtension ( FileSystemMetadata info )
2016-10-29 05:40:15 +00:00
{
if ( info . IsDirectory )
{
return info . Name ;
}
return Path . GetFileNameWithoutExtension ( info . FullName ) ;
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual bool IsPathFile ( string path )
2016-10-29 05:40:15 +00:00
{
2021-11-07 21:32:08 +00:00
if ( path . Contains ( "://" , StringComparison . OrdinalIgnoreCase )
& & ! path . StartsWith ( "file://" , StringComparison . OrdinalIgnoreCase ) )
2016-10-29 05:40:15 +00:00
{
return false ;
}
2017-05-04 18:14:45 +00:00
2016-10-29 05:40:15 +00:00
return true ;
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual void DeleteFile ( string path )
2016-10-29 05:40:15 +00:00
{
2017-05-12 04:54:19 +00:00
SetAttributes ( path , false , false ) ;
2016-10-29 05:40:15 +00:00
File . Delete ( path ) ;
}
2019-02-08 09:13:58 +00:00
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2022-01-22 14:40:05 +00:00
public virtual IEnumerable < FileSystemMetadata > GetDrives ( )
2016-11-01 03:07:45 +00:00
{
2019-05-16 23:45:56 +00:00
// check for ready state to avoid waiting for drives to timeout
// some drives on linux have no actual size or are used for other purposes
2021-11-07 14:33:39 +00:00
return DriveInfo . GetDrives ( )
. Where (
d = > ( d . DriveType = = DriveType . Fixed | | d . DriveType = = DriveType . Network | | d . DriveType = = DriveType . Removable )
& & d . IsReady
& & d . TotalSize ! = 0 )
2019-06-03 04:49:12 +00:00
. Select ( d = > new FileSystemMetadata
2020-03-24 15:12:06 +00:00
{
Name = d . Name ,
FullName = d . RootDirectory . FullName ,
IsDirectory = true
2022-01-22 14:40:05 +00:00
} ) ;
2016-11-01 03:07:45 +00:00
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual IEnumerable < FileSystemMetadata > GetDirectories ( string path , bool recursive = false )
2016-10-29 05:40:15 +00:00
{
2021-01-01 02:40:24 +00:00
return ToMetadata ( new DirectoryInfo ( path ) . EnumerateDirectories ( "*" , GetEnumerationOptions ( recursive ) ) ) ;
2016-10-29 05:40:15 +00:00
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual IEnumerable < FileSystemMetadata > GetFiles ( string path , bool recursive = false )
2017-03-29 06:26:48 +00:00
{
2017-03-30 17:56:32 +00:00
return GetFiles ( path , null , false , recursive ) ;
2017-03-29 06:26:48 +00:00
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2021-05-20 19:28:18 +00:00
public virtual IEnumerable < FileSystemMetadata > GetFiles ( string path , IReadOnlyList < string > ? extensions , bool enableCaseSensitiveExtensions , bool recursive = false )
2016-10-29 05:40:15 +00:00
{
2021-01-01 02:40:24 +00:00
var enumerationOptions = GetEnumerationOptions ( recursive ) ;
2016-10-29 05:40:15 +00:00
2017-03-29 06:26:48 +00:00
// On linux and osx the search pattern is case sensitive
// If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
2019-03-07 14:54:30 +00:00
if ( ( enableCaseSensitiveExtensions | | _isEnvironmentCaseInsensitive ) & & extensions ! = null & & extensions . Count = = 1 )
2017-03-29 06:26:48 +00:00
{
2021-01-01 02:40:24 +00:00
return ToMetadata ( new DirectoryInfo ( path ) . EnumerateFiles ( "*" + extensions [ 0 ] , enumerationOptions ) ) ;
2017-03-29 06:26:48 +00:00
}
2021-01-01 02:40:24 +00:00
var files = new DirectoryInfo ( path ) . EnumerateFiles ( "*" , enumerationOptions ) ;
2017-03-29 06:26:48 +00:00
2019-03-07 14:54:30 +00:00
if ( extensions ! = null & & extensions . Count > 0 )
2017-03-29 06:26:48 +00:00
{
files = files . Where ( i = >
{
2021-05-23 22:30:41 +00:00
var ext = i . Extension . AsSpan ( ) ;
if ( ext . IsEmpty )
2017-03-29 06:26:48 +00:00
{
return false ;
}
2020-06-15 21:43:52 +00:00
2021-05-23 22:30:41 +00:00
return extensions . Contains ( ext , StringComparison . OrdinalIgnoreCase ) ;
2017-03-29 06:26:48 +00:00
} ) ;
}
return ToMetadata ( files ) ;
2016-10-29 05:40:15 +00:00
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual IEnumerable < FileSystemMetadata > GetFileSystemEntries ( string path , bool recursive = false )
2016-10-29 05:40:15 +00:00
{
var directoryInfo = new DirectoryInfo ( path ) ;
2021-01-01 02:40:24 +00:00
var enumerationOptions = GetEnumerationOptions ( recursive ) ;
2016-10-29 05:40:15 +00:00
2021-05-23 22:30:41 +00:00
return ToMetadata ( directoryInfo . EnumerateFileSystemInfos ( "*" , enumerationOptions ) ) ;
2016-10-29 05:40:15 +00:00
}
2017-03-29 06:26:48 +00:00
private IEnumerable < FileSystemMetadata > ToMetadata ( IEnumerable < FileSystemInfo > infos )
2016-10-29 05:40:15 +00:00
{
2017-01-23 21:51:23 +00:00
return infos . Select ( GetFileSystemMetadata ) ;
2016-10-29 05:40:15 +00:00
}
2019-02-08 09:13:58 +00:00
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual IEnumerable < string > GetDirectoryPaths ( string path , bool recursive = false )
2016-10-29 05:40:15 +00:00
{
2021-01-01 02:40:24 +00:00
return Directory . EnumerateDirectories ( path , "*" , GetEnumerationOptions ( recursive ) ) ;
2016-10-29 05:40:15 +00:00
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual IEnumerable < string > GetFilePaths ( string path , bool recursive = false )
2017-03-30 17:56:32 +00:00
{
return GetFilePaths ( path , null , false , recursive ) ;
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2021-05-20 19:28:18 +00:00
public virtual IEnumerable < string > GetFilePaths ( string path , string [ ] ? extensions , bool enableCaseSensitiveExtensions , bool recursive = false )
2016-10-29 05:40:15 +00:00
{
2021-01-01 02:40:24 +00:00
var enumerationOptions = GetEnumerationOptions ( recursive ) ;
2017-03-30 17:56:32 +00:00
// On linux and osx the search pattern is case sensitive
// If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
2018-09-12 17:26:21 +00:00
if ( ( enableCaseSensitiveExtensions | | _isEnvironmentCaseInsensitive ) & & extensions ! = null & & extensions . Length = = 1 )
2017-03-30 17:56:32 +00:00
{
2021-01-01 02:40:24 +00:00
return Directory . EnumerateFiles ( path , "*" + extensions [ 0 ] , enumerationOptions ) ;
2017-03-30 17:56:32 +00:00
}
2021-01-01 02:40:24 +00:00
var files = Directory . EnumerateFiles ( path , "*" , enumerationOptions ) ;
2017-03-30 17:56:32 +00:00
if ( extensions ! = null & & extensions . Length > 0 )
{
files = files . Where ( i = >
{
2021-05-23 22:30:41 +00:00
var ext = Path . GetExtension ( i . AsSpan ( ) ) ;
if ( ext . IsEmpty )
2017-03-30 17:56:32 +00:00
{
return false ;
}
2020-06-15 21:43:52 +00:00
2021-05-23 22:30:41 +00:00
return extensions . Contains ( ext , StringComparison . OrdinalIgnoreCase ) ;
2017-03-30 17:56:32 +00:00
} ) ;
}
return files ;
2016-10-29 05:40:15 +00:00
}
2021-11-07 21:32:08 +00:00
/// <inheritdoc />
2019-01-26 22:24:28 +00:00
public virtual IEnumerable < string > GetFileSystemEntryPaths ( string path , bool recursive = false )
2016-10-29 05:40:15 +00:00
{
2021-01-01 02:40:24 +00:00
return Directory . EnumerateFileSystemEntries ( path , "*" , GetEnumerationOptions ( recursive ) ) ;
}
2022-02-28 23:16:25 +00:00
/// <inheritdoc />
2022-03-03 02:55:44 +00:00
public virtual bool DirectoryExists ( string path )
2022-02-28 23:16:25 +00:00
{
2022-03-03 02:55:44 +00:00
return Directory . Exists ( path ) ;
}
/// <inheritdoc />
public virtual bool FileExists ( string path )
{
return File . Exists ( path ) ;
2022-02-28 23:16:25 +00:00
}
2021-01-01 02:40:24 +00:00
private EnumerationOptions GetEnumerationOptions ( bool recursive )
{
return new EnumerationOptions
{
RecurseSubdirectories = recursive ,
2021-02-02 14:14:11 +00:00
IgnoreInaccessible = true ,
// Don't skip any files.
AttributesToSkip = 0
2021-01-01 02:40:24 +00:00
} ;
2016-10-29 05:40:15 +00:00
}
}
}