2019-01-06 15:50:43 -05:00
using System ;
2016-10-29 01:40:15 -04:00
using System.Collections.Generic ;
2017-10-04 14:51:26 -04:00
using System.Diagnostics ;
2016-10-29 01:40:15 -04:00
using System.IO ;
using System.Linq ;
using System.Text ;
using MediaBrowser.Model.IO ;
2017-03-10 14:51:29 -05:00
using MediaBrowser.Model.System ;
2019-01-13 14:21:32 -05:00
using Microsoft.Extensions.Logging ;
2016-10-29 01:40:15 -04:00
2017-08-16 02:43:41 -04:00
namespace Emby.Server.Implementations.IO
2016-10-29 01:40:15 -04:00
{
/// <summary>
/// Class ManagedFileSystem
/// </summary>
public class ManagedFileSystem : IFileSystem
{
protected ILogger Logger ;
private readonly bool _supportsAsyncFileStreams ;
private char [ ] _invalidFileNameChars ;
private readonly List < IShortcutHandler > _shortcutHandlers = new List < IShortcutHandler > ( ) ;
2018-09-12 13:26:21 -04:00
private bool EnableSeparateFileAndDirectoryQueries ;
2016-10-29 01:40:15 -04:00
2017-01-23 16:51:23 -05:00
private string _tempPath ;
2017-10-04 14:51:26 -04:00
private IEnvironmentInfo _environmentInfo ;
2018-09-12 13:26:21 -04:00
private bool _isEnvironmentCaseInsensitive ;
2017-04-01 20:36:06 -04:00
2018-09-12 13:26:21 -04:00
private string _defaultDirectory ;
public ManagedFileSystem ( ILogger logger , IEnvironmentInfo environmentInfo , string defaultDirectory , string tempPath , bool enableSeparateFileAndDirectoryQueries )
2016-10-29 01:40:15 -04:00
{
Logger = logger ;
2017-03-10 14:51:29 -05:00
_supportsAsyncFileStreams = true ;
2017-01-23 16:51:23 -05:00
_tempPath = tempPath ;
2017-10-04 14:51:26 -04:00
_environmentInfo = environmentInfo ;
2018-09-12 13:26:21 -04:00
_defaultDirectory = defaultDirectory ;
2017-03-18 19:37:29 -04:00
2018-09-12 13:26:21 -04:00
// On Linux with mono, this needs to be true or symbolic links are ignored
EnableSeparateFileAndDirectoryQueries = enableSeparateFileAndDirectoryQueries ;
2017-03-18 19:37:29 -04:00
2017-03-10 14:51:29 -05:00
SetInvalidFileNameChars ( environmentInfo . OperatingSystem = = MediaBrowser . Model . System . OperatingSystem . Windows ) ;
2017-04-01 20:36:06 -04:00
2018-09-12 13:26:21 -04:00
_isEnvironmentCaseInsensitive = environmentInfo . OperatingSystem = = MediaBrowser . Model . System . OperatingSystem . Windows ;
}
public string DefaultDirectory
{
get
{
var value = _defaultDirectory ;
if ( ! string . IsNullOrEmpty ( value ) )
{
try
{
if ( DirectoryExists ( value ) )
{
return value ;
}
}
catch
{
}
}
return null ;
}
2016-10-29 01:40:15 -04:00
}
public void AddShortcutHandler ( IShortcutHandler handler )
{
_shortcutHandlers . Add ( handler ) ;
}
protected void SetInvalidFileNameChars ( bool enableManagedInvalidFileNameChars )
{
if ( enableManagedInvalidFileNameChars )
{
_invalidFileNameChars = Path . GetInvalidFileNameChars ( ) ;
}
else
{
2018-09-12 13:26:21 -04:00
// Be consistent across platforms because the windows server will fail to query network shares that don't follow windows conventions
// https://referencesource.microsoft.com/#mscorlib/system/io/path.cs
2019-01-06 15:50:43 -05:00
_invalidFileNameChars = new char [ ] { '\"' , '<' , '>' , '|' , '\0' , ( char ) 1 , ( char ) 2 , ( char ) 3 , ( char ) 4 , ( char ) 5 , ( char ) 6 , ( char ) 7 , ( char ) 8 , ( char ) 9 , ( char ) 10 , ( char ) 11 , ( char ) 12 , ( char ) 13 , ( char ) 14 , ( char ) 15 , ( char ) 16 , ( char ) 17 , ( char ) 18 , ( char ) 19 , ( char ) 20 , ( char ) 21 , ( char ) 22 , ( char ) 23 , ( char ) 24 , ( char ) 25 , ( char ) 26 , ( char ) 27 , ( char ) 28 , ( char ) 29 , ( char ) 30 , ( char ) 31 , ':' , '*' , '?' , '\\' , '/' } ;
2016-10-29 01:40:15 -04:00
}
}
2019-01-06 15:50:43 -05:00
public char DirectorySeparatorChar = > Path . DirectorySeparatorChar ;
2016-10-29 01:40:15 -04:00
public string GetFullPath ( string path )
{
return Path . GetFullPath ( path ) ;
}
/// <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>
2019-01-13 15:37:13 -05:00
/// <exception cref="ArgumentNullException">filename</exception>
2016-10-29 01:40:15 -04:00
public virtual bool IsShortcut ( string filename )
{
if ( string . IsNullOrEmpty ( filename ) )
{
2019-01-06 15:50:43 -05:00
throw new ArgumentNullException ( nameof ( filename ) ) ;
2016-10-29 01:40:15 -04:00
}
var extension = Path . GetExtension ( filename ) ;
return _shortcutHandlers . Any ( i = > string . Equals ( extension , i . Extension , StringComparison . OrdinalIgnoreCase ) ) ;
}
/// <summary>
/// Resolves the shortcut.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>System.String.</returns>
2019-01-13 15:37:13 -05:00
/// <exception cref="ArgumentNullException">filename</exception>
2016-10-29 01:40:15 -04:00
public virtual string ResolveShortcut ( string filename )
{
if ( string . IsNullOrEmpty ( filename ) )
{
2019-01-06 15:50:43 -05:00
throw new ArgumentNullException ( nameof ( filename ) ) ;
2016-10-29 01:40:15 -04:00
}
var extension = Path . GetExtension ( filename ) ;
var handler = _shortcutHandlers . FirstOrDefault ( i = > string . Equals ( extension , i . Extension , StringComparison . OrdinalIgnoreCase ) ) ;
if ( handler ! = null )
{
return handler . Resolve ( filename ) ;
}
return null ;
}
2018-09-12 13:26:21 -04:00
public string MakeAbsolutePath ( string folderPath , string filePath )
{
2019-01-06 15:50:43 -05:00
if ( string . IsNullOrWhiteSpace ( filePath ) ) return filePath ;
2018-09-12 13:26:21 -04:00
if ( filePath . Contains ( @"://" ) ) return filePath ; //stream
if ( filePath . Length > 3 & & filePath [ 1 ] = = ':' & & filePath [ 2 ] = = '/' ) return filePath ; //absolute local path
// unc path
if ( filePath . StartsWith ( "\\\\" ) )
{
return filePath ;
}
var firstChar = filePath [ 0 ] ;
if ( firstChar = = '/' )
{
2019-01-07 18:27:46 -05:00
// For this we don't really know.
2018-09-12 13:26:21 -04:00
return filePath ;
}
if ( firstChar = = '\\' ) //relative path
{
filePath = filePath . Substring ( 1 ) ;
}
try
{
string path = System . IO . Path . Combine ( folderPath , filePath ) ;
path = System . IO . Path . GetFullPath ( path ) ;
return path ;
}
2018-12-20 07:11:26 -05:00
catch ( ArgumentException )
2018-09-12 13:26:21 -04:00
{
return filePath ;
}
catch ( PathTooLongException )
{
return filePath ;
}
catch ( NotSupportedException )
{
return filePath ;
}
}
2016-10-29 01:40:15 -04:00
/// <summary>
/// Creates the shortcut.
/// </summary>
/// <param name="shortcutPath">The shortcut path.</param>
/// <param name="target">The target.</param>
2019-01-13 15:37:13 -05:00
/// <exception cref="ArgumentNullException">
2016-10-29 01:40:15 -04:00
/// shortcutPath
/// or
/// target
/// </exception>
public void CreateShortcut ( string shortcutPath , string target )
{
if ( string . IsNullOrEmpty ( shortcutPath ) )
{
2019-01-06 15:50:43 -05:00
throw new ArgumentNullException ( nameof ( shortcutPath ) ) ;
2016-10-29 01:40:15 -04:00
}
if ( string . IsNullOrEmpty ( target ) )
{
2019-01-06 15:50:43 -05:00
throw new ArgumentNullException ( nameof ( target ) ) ;
2016-10-29 01:40:15 -04:00
}
var extension = Path . GetExtension ( shortcutPath ) ;
var handler = _shortcutHandlers . FirstOrDefault ( i = > string . Equals ( extension , i . Extension , StringComparison . OrdinalIgnoreCase ) ) ;
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>
public FileSystemMetadata GetFileSystemInfo ( string path )
{
// 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>
public FileSystemMetadata GetFileInfo ( string path )
{
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>
public FileSystemMetadata GetDirectoryInfo ( string path )
{
var fileInfo = new DirectoryInfo ( path ) ;
return GetFileSystemMetadata ( fileInfo ) ;
}
private FileSystemMetadata GetFileSystemMetadata ( FileSystemInfo info )
{
var result = new FileSystemMetadata ( ) ;
result . Exists = info . Exists ;
result . FullName = info . FullName ;
result . Extension = info . Extension ;
result . Name = info . Name ;
if ( result . Exists )
{
2018-09-12 13:26:21 -04:00
result . IsDirectory = info is DirectoryInfo | | ( info . Attributes & FileAttributes . Directory ) = = FileAttributes . Directory ;
//if (!result.IsDirectory)
//{
// result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
//}
2016-10-29 01:40:15 -04:00
var fileInfo = info as FileInfo ;
if ( fileInfo ! = null )
{
result . Length = fileInfo . Length ;
result . DirectoryName = fileInfo . DirectoryName ;
}
result . CreationTimeUtc = GetCreationTimeUtc ( info ) ;
result . LastWriteTimeUtc = GetLastWriteTimeUtc ( info ) ;
}
else
{
result . IsDirectory = info is DirectoryInfo ;
}
return result ;
}
2019-01-06 15:50:43 -05:00
private static ExtendedFileSystemInfo GetExtendedFileSystemInfo ( string path )
2018-09-12 13:26:21 -04: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 01:40:15 -04:00
/// <summary>
/// The space char
/// </summary>
private const char SpaceChar = ' ' ;
/// <summary>
/// Takes a filename and removes invalid characters
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>System.String.</returns>
2019-01-13 15:37:13 -05:00
/// <exception cref="ArgumentNullException">filename</exception>
2016-10-29 01:40:15 -04:00
public string GetValidFilename ( string filename )
{
var builder = new StringBuilder ( filename ) ;
foreach ( var c in _invalidFileNameChars )
{
builder = builder . Replace ( c , SpaceChar ) ;
}
return builder . ToString ( ) ;
}
/// <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 )
{
2018-12-20 07:11:26 -05:00
Logger . LogError ( ex , "Error determining CreationTimeUtc for {FullName}" , info . FullName ) ;
2016-10-29 01:40:15 -04:00
return DateTime . MinValue ;
}
}
/// <summary>
/// Gets the creation time UTC.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>DateTime.</returns>
public DateTime GetCreationTimeUtc ( string path )
{
return GetCreationTimeUtc ( GetFileSystemInfo ( path ) ) ;
}
public DateTime GetCreationTimeUtc ( FileSystemMetadata info )
{
return info . CreationTimeUtc ;
}
public DateTime GetLastWriteTimeUtc ( FileSystemMetadata info )
{
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 )
{
2018-12-20 07:11:26 -05:00
Logger . LogError ( ex , "Error determining LastAccessTimeUtc for {FullName}" , info . FullName ) ;
2016-10-29 01:40:15 -04:00
return DateTime . MinValue ;
}
}
/// <summary>
/// Gets the last write time UTC.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>DateTime.</returns>
public DateTime GetLastWriteTimeUtc ( string path )
{
return GetLastWriteTimeUtc ( GetFileSystemInfo ( path ) ) ;
}
/// <summary>
/// Gets the file stream.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="mode">The mode.</param>
/// <param name="access">The access.</param>
/// <param name="share">The share.</param>
/// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
/// <returns>FileStream.</returns>
public Stream GetFileStream ( string path , FileOpenMode mode , FileAccessMode access , FileShareMode share , bool isAsync = false )
{
if ( _supportsAsyncFileStreams & & isAsync )
{
2017-05-09 14:51:26 -04:00
return GetFileStream ( path , mode , access , share , FileOpenOptions . Asynchronous ) ;
2016-10-29 01:40:15 -04:00
}
2017-05-09 14:51:26 -04:00
return GetFileStream ( path , mode , access , share , FileOpenOptions . None ) ;
}
public Stream GetFileStream ( string path , FileOpenMode mode , FileAccessMode access , FileShareMode share , FileOpenOptions fileOpenOptions )
{
var defaultBufferSize = 4096 ;
return new FileStream ( path , GetFileMode ( mode ) , GetFileAccess ( access ) , GetFileShare ( share ) , defaultBufferSize , GetFileOptions ( fileOpenOptions ) ) ;
}
2019-01-06 15:50:43 -05:00
private static FileOptions GetFileOptions ( FileOpenOptions mode )
2017-05-09 14:51:26 -04:00
{
var val = ( int ) mode ;
return ( FileOptions ) val ;
2016-10-29 01:40:15 -04:00
}
2019-01-06 15:50:43 -05:00
private static FileMode GetFileMode ( FileOpenMode mode )
2016-10-29 01:40:15 -04:00
{
switch ( mode )
{
2017-04-01 20:36:06 -04:00
//case FileOpenMode.Append:
// return FileMode.Append;
2016-10-29 01:40:15 -04:00
case FileOpenMode . Create :
return FileMode . Create ;
case FileOpenMode . CreateNew :
return FileMode . CreateNew ;
case FileOpenMode . Open :
return FileMode . Open ;
case FileOpenMode . OpenOrCreate :
return FileMode . OpenOrCreate ;
2017-04-01 20:36:06 -04:00
//case FileOpenMode.Truncate:
// return FileMode.Truncate;
2016-10-29 01:40:15 -04:00
default :
throw new Exception ( "Unrecognized FileOpenMode" ) ;
}
}
2019-01-06 15:50:43 -05:00
private static FileAccess GetFileAccess ( FileAccessMode mode )
2016-10-29 01:40:15 -04:00
{
2016-11-27 14:36:56 -05:00
switch ( mode )
{
2017-04-01 20:36:06 -04:00
//case FileAccessMode.ReadWrite:
// return FileAccess.ReadWrite;
2016-11-27 14:36:56 -05:00
case FileAccessMode . Write :
return FileAccess . Write ;
case FileAccessMode . Read :
return FileAccess . Read ;
default :
throw new Exception ( "Unrecognized FileAccessMode" ) ;
}
2016-10-29 01:40:15 -04:00
}
2019-01-06 15:50:43 -05:00
private static FileShare GetFileShare ( FileShareMode mode )
2016-10-29 01:40:15 -04:00
{
2016-11-27 14:36:56 -05:00
switch ( mode )
{
case FileShareMode . ReadWrite :
return FileShare . ReadWrite ;
case FileShareMode . Write :
return FileShare . Write ;
case FileShareMode . Read :
return FileShare . Read ;
case FileShareMode . None :
return FileShare . None ;
default :
throw new Exception ( "Unrecognized FileShareMode" ) ;
}
2016-10-29 01:40:15 -04:00
}
public void SetHidden ( string path , bool isHidden )
{
2017-11-25 23:48:12 -05:00
if ( _environmentInfo . OperatingSystem ! = MediaBrowser . Model . System . OperatingSystem . Windows )
{
return ;
}
2018-09-12 13:26:21 -04:00
var info = GetExtendedFileSystemInfo ( path ) ;
2016-10-29 01:40:15 -04:00
if ( info . Exists & & info . IsHidden ! = isHidden )
{
if ( isHidden )
{
2016-10-31 00:28:23 -04:00
File . SetAttributes ( path , File . GetAttributes ( path ) | FileAttributes . Hidden ) ;
2016-10-29 01:40:15 -04:00
}
else
{
2019-01-13 15:37:13 -05:00
var attributes = File . GetAttributes ( path ) ;
2016-10-31 00:28:23 -04:00
attributes = RemoveAttribute ( attributes , FileAttributes . Hidden ) ;
File . SetAttributes ( path , attributes ) ;
2016-10-29 01:40:15 -04:00
}
}
}
2016-11-09 12:24:57 -05:00
public void SetReadOnly ( string path , bool isReadOnly )
{
2017-11-25 23:48:12 -05:00
if ( _environmentInfo . OperatingSystem ! = MediaBrowser . Model . System . OperatingSystem . Windows )
{
return ;
}
2018-09-12 13:26:21 -04:00
var info = GetExtendedFileSystemInfo ( path ) ;
2016-11-09 12:24:57 -05:00
if ( info . Exists & & info . IsReadOnly ! = isReadOnly )
{
if ( isReadOnly )
{
File . SetAttributes ( path , File . GetAttributes ( path ) | FileAttributes . ReadOnly ) ;
}
else
{
2019-01-13 15:37:13 -05:00
var attributes = File . GetAttributes ( path ) ;
2016-11-09 12:24:57 -05:00
attributes = RemoveAttribute ( attributes , FileAttributes . ReadOnly ) ;
File . SetAttributes ( path , attributes ) ;
}
}
}
2017-05-12 00:54:19 -04:00
public void SetAttributes ( string path , bool isHidden , bool isReadOnly )
{
2017-11-25 23:48:12 -05:00
if ( _environmentInfo . OperatingSystem ! = MediaBrowser . Model . System . OperatingSystem . Windows )
{
return ;
}
2018-09-12 13:26:21 -04:00
var info = GetExtendedFileSystemInfo ( path ) ;
2017-05-12 00:54:19 -04:00
if ( ! info . Exists )
{
return ;
}
if ( info . IsReadOnly = = isReadOnly & & info . IsHidden = = isHidden )
{
return ;
}
var attributes = File . GetAttributes ( path ) ;
if ( isReadOnly )
{
attributes = attributes | FileAttributes . ReadOnly ;
}
else
{
attributes = RemoveAttribute ( attributes , FileAttributes . ReadOnly ) ;
}
if ( isHidden )
{
attributes = attributes | FileAttributes . Hidden ;
}
else
{
attributes = RemoveAttribute ( attributes , FileAttributes . Hidden ) ;
}
File . SetAttributes ( path , attributes ) ;
}
2016-10-29 01:40:15 -04: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>
public void SwapFiles ( string file1 , string file2 )
{
if ( string . IsNullOrEmpty ( file1 ) )
{
2019-01-06 15:50:43 -05:00
throw new ArgumentNullException ( nameof ( file1 ) ) ;
2016-10-29 01:40:15 -04:00
}
if ( string . IsNullOrEmpty ( file2 ) )
{
2019-01-06 15:50:43 -05:00
throw new ArgumentNullException ( nameof ( file2 ) ) ;
2016-10-29 01:40:15 -04:00
}
2017-01-23 16:51:23 -05:00
var temp1 = Path . Combine ( _tempPath , Guid . NewGuid ( ) . ToString ( "N" ) ) ;
2016-10-29 01:40:15 -04:00
// Copying over will fail against hidden files
2016-12-27 02:25:51 -05:00
SetHidden ( file1 , false ) ;
SetHidden ( file2 , false ) ;
2016-10-29 01:40:15 -04:00
2017-01-23 16:51:23 -05:00
Directory . CreateDirectory ( _tempPath ) ;
2016-10-29 01:40:15 -04:00
CopyFile ( file1 , temp1 , true ) ;
2016-12-17 15:52:05 -05:00
CopyFile ( file2 , file1 , true ) ;
2016-10-29 01:40:15 -04:00
CopyFile ( temp1 , file2 , true ) ;
}
2019-01-06 15:50:43 -05:00
private static char GetDirectorySeparatorChar ( string path )
2017-04-02 00:08:07 -04:00
{
2017-04-02 00:45:57 -04:00
return Path . DirectorySeparatorChar ;
2017-04-02 00:08:07 -04:00
}
2016-10-29 01:40:15 -04:00
public bool ContainsSubPath ( string parentPath , string path )
{
if ( string . IsNullOrEmpty ( parentPath ) )
{
2019-01-06 15:50:43 -05:00
throw new ArgumentNullException ( nameof ( parentPath ) ) ;
2016-10-29 01:40:15 -04:00
}
if ( string . IsNullOrEmpty ( path ) )
{
2019-01-06 15:50:43 -05:00
throw new ArgumentNullException ( nameof ( path ) ) ;
2016-10-29 01:40:15 -04:00
}
2017-04-02 00:45:57 -04:00
var separatorChar = GetDirectorySeparatorChar ( parentPath ) ;
2017-04-02 00:08:07 -04:00
return path . IndexOf ( parentPath . TrimEnd ( separatorChar ) + separatorChar , StringComparison . OrdinalIgnoreCase ) ! = - 1 ;
2016-10-29 01:40:15 -04:00
}
public bool IsRootPath ( string path )
{
if ( string . IsNullOrEmpty ( path ) )
{
2019-01-06 15:50:43 -05:00
throw new ArgumentNullException ( nameof ( path ) ) ;
2016-10-29 01:40:15 -04:00
}
2017-05-04 14:14:45 -04:00
var parent = GetDirectoryName ( path ) ;
2016-10-29 01:40:15 -04:00
if ( ! string . IsNullOrEmpty ( parent ) )
{
return false ;
}
return true ;
}
2017-05-04 14:14:45 -04:00
public string GetDirectoryName ( string path )
{
return Path . GetDirectoryName ( path ) ;
}
2016-10-29 01:40:15 -04:00
public string NormalizePath ( string path )
{
if ( string . IsNullOrEmpty ( path ) )
{
2019-01-06 15:50:43 -05:00
throw new ArgumentNullException ( nameof ( path ) ) ;
2016-10-29 01:40:15 -04:00
}
if ( path . EndsWith ( ":\\" , StringComparison . OrdinalIgnoreCase ) )
{
return path ;
}
2017-04-02 00:45:57 -04:00
return path . TrimEnd ( GetDirectorySeparatorChar ( path ) ) ;
2016-10-29 01:40:15 -04:00
}
2017-05-04 14:14:45 -04:00
public bool AreEqual ( string path1 , string path2 )
{
if ( path1 = = null & & path2 = = null )
{
return true ;
}
if ( path1 = = null | | path2 = = null )
{
return false ;
}
return string . Equals ( NormalizePath ( path1 ) , NormalizePath ( path2 ) , StringComparison . OrdinalIgnoreCase ) ;
}
2016-10-29 01:40:15 -04:00
public string GetFileNameWithoutExtension ( FileSystemMetadata info )
{
if ( info . IsDirectory )
{
return info . Name ;
}
return Path . GetFileNameWithoutExtension ( info . FullName ) ;
}
public string GetFileNameWithoutExtension ( string path )
{
return Path . GetFileNameWithoutExtension ( path ) ;
}
public bool IsPathFile ( string path )
{
// Cannot use Path.IsPathRooted because it returns false under mono when using windows-based paths, e.g. C:\\
if ( path . IndexOf ( "://" , StringComparison . OrdinalIgnoreCase ) ! = - 1 & &
! path . StartsWith ( "file://" , StringComparison . OrdinalIgnoreCase ) )
{
return false ;
}
2017-05-04 14:14:45 -04:00
2016-10-29 01:40:15 -04:00
return true ;
//return Path.IsPathRooted(path);
}
public void DeleteFile ( string path )
{
2017-05-12 00:54:19 -04:00
SetAttributes ( path , false , false ) ;
2016-10-29 01:40:15 -04:00
File . Delete ( path ) ;
}
public void DeleteDirectory ( string path , bool recursive )
{
Directory . Delete ( path , recursive ) ;
}
public void CreateDirectory ( string path )
{
Directory . CreateDirectory ( path ) ;
}
2016-10-31 23:07:45 -04:00
public List < FileSystemMetadata > GetDrives ( )
{
// Only include drives in the ready state or this method could end up being very slow, waiting for drives to timeout
return DriveInfo . GetDrives ( ) . Where ( d = > d . IsReady ) . Select ( d = > new FileSystemMetadata
{
Name = GetName ( d ) ,
FullName = d . RootDirectory . FullName ,
IsDirectory = true
} ) . ToList ( ) ;
}
2019-01-06 15:50:43 -05:00
private static string GetName ( DriveInfo drive )
2016-10-31 23:07:45 -04:00
{
return drive . Name ;
}
2016-10-29 01:40:15 -04:00
public IEnumerable < FileSystemMetadata > GetDirectories ( string path , bool recursive = false )
{
var searchOption = recursive ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly ;
2017-03-29 02:26:48 -04:00
return ToMetadata ( new DirectoryInfo ( path ) . EnumerateDirectories ( "*" , searchOption ) ) ;
2016-10-29 01:40:15 -04:00
}
public IEnumerable < FileSystemMetadata > GetFiles ( string path , bool recursive = false )
2017-03-29 02:26:48 -04:00
{
2017-03-30 13:56:32 -04:00
return GetFiles ( path , null , false , recursive ) ;
2017-03-29 02:26:48 -04:00
}
public IEnumerable < FileSystemMetadata > GetFiles ( string path , string [ ] extensions , bool enableCaseSensitiveExtensions , bool recursive = false )
2016-10-29 01:40:15 -04:00
{
var searchOption = recursive ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly ;
2017-03-29 02:26:48 -04: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 13:26:21 -04:00
if ( ( enableCaseSensitiveExtensions | | _isEnvironmentCaseInsensitive ) & & extensions ! = null & & extensions . Length = = 1 )
2017-03-29 02:26:48 -04:00
{
return ToMetadata ( new DirectoryInfo ( path ) . EnumerateFiles ( "*" + extensions [ 0 ] , searchOption ) ) ;
}
var files = new DirectoryInfo ( path ) . EnumerateFiles ( "*" , searchOption ) ;
if ( extensions ! = null & & extensions . Length > 0 )
{
files = files . Where ( i = >
{
var ext = i . Extension ;
if ( ext = = null )
{
return false ;
}
return extensions . Contains ( ext , StringComparer . OrdinalIgnoreCase ) ;
} ) ;
}
return ToMetadata ( files ) ;
2016-10-29 01:40:15 -04:00
}
public IEnumerable < FileSystemMetadata > GetFileSystemEntries ( string path , bool recursive = false )
{
var directoryInfo = new DirectoryInfo ( path ) ;
var searchOption = recursive ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly ;
2018-09-12 13:26:21 -04:00
if ( EnableSeparateFileAndDirectoryQueries )
2016-10-29 01:40:15 -04:00
{
2017-03-29 02:26:48 -04:00
return ToMetadata ( directoryInfo . EnumerateDirectories ( "*" , searchOption ) )
. Concat ( ToMetadata ( directoryInfo . EnumerateFiles ( "*" , searchOption ) ) ) ;
2016-10-29 01:40:15 -04:00
}
2017-03-29 02:26:48 -04:00
return ToMetadata ( directoryInfo . EnumerateFileSystemInfos ( "*" , searchOption ) ) ;
2016-10-29 01:40:15 -04:00
}
2017-03-29 02:26:48 -04:00
private IEnumerable < FileSystemMetadata > ToMetadata ( IEnumerable < FileSystemInfo > infos )
2016-10-29 01:40:15 -04:00
{
2017-01-23 16:51:23 -05:00
return infos . Select ( GetFileSystemMetadata ) ;
2016-10-29 01:40:15 -04:00
}
2016-11-04 04:43:59 -04:00
public string [ ] ReadAllLines ( string path )
{
return File . ReadAllLines ( path ) ;
}
public void WriteAllLines ( string path , IEnumerable < string > lines )
{
File . WriteAllLines ( path , lines ) ;
}
2016-10-29 01:40:15 -04:00
public Stream OpenRead ( string path )
{
return File . OpenRead ( path ) ;
}
2018-09-12 13:26:21 -04:00
private void CopyFileUsingStreams ( string source , string target , bool overwrite )
{
using ( var sourceStream = OpenRead ( source ) )
{
using ( var targetStream = GetFileStream ( target , FileOpenMode . Create , FileAccessMode . Write , FileShareMode . Read ) )
{
sourceStream . CopyTo ( targetStream ) ;
}
}
}
2016-10-29 01:40:15 -04:00
public void CopyFile ( string source , string target , bool overwrite )
{
File . Copy ( source , target , overwrite ) ;
}
public void MoveFile ( string source , string target )
{
File . Move ( source , target ) ;
}
public void MoveDirectory ( string source , string target )
{
Directory . Move ( source , target ) ;
}
public bool DirectoryExists ( string path )
{
return Directory . Exists ( path ) ;
}
public bool FileExists ( string path )
{
return File . Exists ( path ) ;
}
public string ReadAllText ( string path )
{
return File . ReadAllText ( path ) ;
}
public byte [ ] ReadAllBytes ( string path )
{
return File . ReadAllBytes ( path ) ;
}
public void WriteAllText ( string path , string text , Encoding encoding )
{
File . WriteAllText ( path , text , encoding ) ;
}
public void WriteAllText ( string path , string text )
{
File . WriteAllText ( path , text ) ;
}
public void WriteAllBytes ( string path , byte [ ] bytes )
{
File . WriteAllBytes ( path , bytes ) ;
}
public string ReadAllText ( string path , Encoding encoding )
{
return File . ReadAllText ( path , encoding ) ;
}
public IEnumerable < string > GetDirectoryPaths ( string path , bool recursive = false )
{
var searchOption = recursive ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly ;
return Directory . EnumerateDirectories ( path , "*" , searchOption ) ;
}
public IEnumerable < string > GetFilePaths ( string path , bool recursive = false )
2017-03-30 13:56:32 -04:00
{
return GetFilePaths ( path , null , false , recursive ) ;
}
public IEnumerable < string > GetFilePaths ( string path , string [ ] extensions , bool enableCaseSensitiveExtensions , bool recursive = false )
2016-10-29 01:40:15 -04:00
{
var searchOption = recursive ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly ;
2017-03-30 13:56:32 -04: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 13:26:21 -04:00
if ( ( enableCaseSensitiveExtensions | | _isEnvironmentCaseInsensitive ) & & extensions ! = null & & extensions . Length = = 1 )
2017-03-30 13:56:32 -04:00
{
return Directory . EnumerateFiles ( path , "*" + extensions [ 0 ] , searchOption ) ;
}
var files = Directory . EnumerateFiles ( path , "*" , searchOption ) ;
if ( extensions ! = null & & extensions . Length > 0 )
{
files = files . Where ( i = >
{
var ext = Path . GetExtension ( i ) ;
if ( ext = = null )
{
return false ;
}
return extensions . Contains ( ext , StringComparer . OrdinalIgnoreCase ) ;
} ) ;
}
return files ;
2016-10-29 01:40:15 -04:00
}
public IEnumerable < string > GetFileSystemEntryPaths ( string path , bool recursive = false )
{
var searchOption = recursive ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly ;
return Directory . EnumerateFileSystemEntries ( path , "*" , searchOption ) ;
}
2016-11-11 02:24:36 -05:00
public virtual void SetExecutable ( string path )
{
2017-10-04 14:51:26 -04:00
if ( _environmentInfo . OperatingSystem = = MediaBrowser . Model . System . OperatingSystem . OSX )
{
RunProcess ( "chmod" , "+x \"" + path + "\"" , GetDirectoryName ( path ) ) ;
}
}
2017-03-29 02:26:48 -04:00
2019-01-06 15:50:43 -05:00
private static void RunProcess ( string path , string args , string workingDirectory )
2017-10-04 14:51:26 -04:00
{
using ( var process = Process . Start ( new ProcessStartInfo
{
Arguments = args ,
FileName = path ,
CreateNoWindow = true ,
WorkingDirectory = workingDirectory ,
WindowStyle = ProcessWindowStyle . Normal
} ) )
{
process . WaitForExit ( ) ;
}
2016-11-11 02:24:36 -05:00
}
2016-10-29 01:40:15 -04:00
}
}