2024-04-15 13:17:49 +02:00
// Copyright (C) 2024, The Duplicati Team
2024-02-28 15:45:30 +01:00
// https://duplicati.com, hello@duplicati.com
2013-02-12 21:43:14 +00:00
//
2024-02-28 15:45:30 +01:00
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
2013-02-12 21:43:14 +00:00
//
2024-02-28 15:45:30 +01:00
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
2013-02-12 21:43:14 +00:00
//
2024-02-28 15:45:30 +01:00
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
2013-02-12 21:43:14 +00:00
using System ;
using System.Collections.Generic ;
2018-04-19 22:59:28 +02:00
using System.IO ;
2018-05-13 12:48:00 +02:00
using System.Linq ;
2021-03-15 18:04:39 -06:00
using System.Threading ;
2018-03-20 13:41:57 -06:00
using System.Threading.Tasks ;
2018-05-13 12:48:00 +02:00
using System.Text ;
using System.Text.RegularExpressions ;
2019-09-01 09:47:04 -07:00
using Duplicati.Library.Common.IO ;
using Duplicati.Library.Common ;
2018-12-11 21:07:30 -08:00
using System.Globalization ;
2018-09-09 14:42:40 +02:00
using System.Security.Cryptography ;
2018-12-11 21:07:30 -08:00
2013-02-12 21:43:14 +00:00
namespace Duplicati.Library.Utility
{
public static class Utility
{
/// <summary>
/// Size of buffers for copying stream
/// </summary>
2017-09-04 11:03:53 +02:00
public static long DEFAULT_BUFFER_SIZE => SystemContextSettings . Buffersize ;
2021-03-28 23:20:20 -06:00
2018-04-10 17:39:57 -07:00
/// <summary>
/// A cache of the FileSystemCaseSensitive property, which is computed upon the first access.
/// </summary>
2018-04-20 17:30:08 +02:00
private static bool? CachedIsFSCaseSensitive ;
2018-04-10 17:39:57 -07:00
2013-02-12 21:43:14 +00:00
/// <summary>
/// The EPOCH offset (unix style)
/// </summary>
2013-07-17 13:31:17 +02:00
public static readonly DateTime EPOCH = new DateTime ( 1970 , 1 , 1 , 0 , 0 , 0 , DateTimeKind . Utc );
2013-02-12 21:43:14 +00:00
/// <summary>
/// The attribute value used to indicate error
/// </summary>
2018-04-20 17:30:08 +02:00
public const FileAttributes ATTRIBUTE_ERROR = ( FileAttributes )( 1 << 30 );
2013-02-12 21:43:14 +00:00
/// <summary>
/// The callback delegate type used to collecting file information
/// </summary>
/// <param name="rootpath">The path that the file enumeration started at</param>
/// <param name="path">The current element</param>
/// <param name="attributes">The attributes of the element</param>
/// <returns>A value indicating if the folder should be recursed, ignored for other types</returns>
2018-04-20 17:30:08 +02:00
public delegate bool EnumerationFilterDelegate ( string rootpath , string path , FileAttributes attributes );
2013-02-12 21:43:14 +00:00
/// <summary>
/// Copies the content of one stream into another
/// </summary>
/// <param name="source">The stream to read from</param>
/// <param name="target">The stream to write to</param>
2018-06-08 11:21:43 +02:00
public static long CopyStream ( Stream source , Stream target )
2013-02-12 21:43:14 +00:00
{
2018-06-08 11:21:43 +02:00
return CopyStream ( source , target , true );
2013-02-12 21:43:14 +00:00
}
/// <summary>
/// Copies the content of one stream into another
/// </summary>
/// <param name="source">The stream to read from</param>
/// <param name="target">The stream to write to</param>
/// <param name="tryRewindSource">True if an attempt should be made to rewind the source stream, false otherwise</param>
2018-04-20 17:30:08 +02:00
/// <param name="buf">Temporary buffer to use (optional)</param>
2018-06-08 11:21:43 +02:00
public static long CopyStream ( Stream source , Stream target , bool tryRewindSource , byte [] buf = null )
2013-02-12 21:43:14 +00:00
{
if ( tryRewindSource && source . CanSeek )
try { source . Position = 0 ; }
2018-04-20 17:30:08 +02:00
catch
{
// ignored
}
2013-02-12 21:43:14 +00:00
2021-03-28 23:20:20 -06:00
buf = buf ?? new byte [ DEFAULT_BUFFER_SIZE ];
2014-11-10 18:38:18 +01:00
int read ;
2018-06-08 10:31:27 +02:00
long total = 0 ;
2018-05-09 17:21:15 +02:00
while (( read = source . Read ( buf , 0 , buf . Length )) != 0 )
2018-06-08 10:31:27 +02:00
{
2018-05-09 17:21:15 +02:00
target . Write ( buf , 0 , read );
total += read ;
2021-03-15 18:04:39 -06:00
}
2021-03-28 23:20:20 -06:00
2021-03-15 18:04:39 -06:00
return total ;
2013-02-12 21:43:14 +00:00
}
2019-02-17 12:03:07 -06:00
/// <summary>
/// Copies the content of one stream into another
/// </summary>
/// <param name="source">The stream to read from</param>
/// <param name="target">The stream to write to</param>
/// <param name="cancelToken">Token to cancel the operation.</param>
public static async Task < long > CopyStreamAsync ( Stream source , Stream target , CancellationToken cancelToken )
{
return await CopyStreamAsync ( source , target , tryRewindSource : true , cancelToken : cancelToken ). ConfigureAwait ( false );
}
/// <summary>
/// Copies the content of one stream into another
/// </summary>
/// <param name="source">The stream to read from</param>
/// <param name="target">The stream to write to</param>
/// <param name="tryRewindSource">True if an attempt should be made to rewind the source stream, false otherwise</param>
/// <param name="cancelToken">Token to cancel the operation.</param>
/// <param name="buf">Temporary buffer to use (optional)</param>
public static async Task < long > CopyStreamAsync ( Stream source , Stream target , bool tryRewindSource , CancellationToken cancelToken , byte [] buf = null )
{
if ( tryRewindSource && source . CanSeek )
try { source . Position = 0 ; }
catch {}
2021-03-28 23:20:20 -06:00
buf = buf ?? new byte [ DEFAULT_BUFFER_SIZE ];
2019-02-17 12:03:07 -06:00
int read ;
long total = 0 ;
while ( true )
{
read = await source . ReadAsync ( buf , 0 , buf . Length , cancelToken ). ConfigureAwait ( false );
if ( read == 0 ) break ;
await target . WriteAsync ( buf , 0 , read , cancelToken ). ConfigureAwait ( false );
total += read ;
}
2022-03-18 11:03:23 -07:00
2019-02-17 12:03:07 -06:00
return total ;
}
2022-03-18 11:03:23 -07:00
/// <summary>
/// Get the length of a stream.
/// Attempt to use the stream's Position property if allowPositionFallback is <c>true</c> (only valid if stream is at the end).
/// </summary>
/// <param name="stream">Stream to get the length of.</param>
/// <param name="allowPositionFallback">Attempt to use the Position property if <c>true</c> and the Length property is not available (only valid if stream is at the end).</param>
/// <returns>Returns the stream's length, if available, or null if not supported by the stream.</returns>
public static long? GetStreamLength ( Stream stream , bool allowPositionFallback = true )
{
return GetStreamLength ( stream , out bool _ , allowPositionFallback );
}
/// <summary>
/// Get the length of a stream.
/// Attempt to use the stream's Position property if allowPositionFallback is <c>true</c> (only valid if stream is at the end).
/// </summary>
/// <param name="stream">Stream to get the length of.</param>
/// <param name="isStreamPosition">Indicates if the Position value was used instead of Length.</param>
/// <param name="allowPositionFallback">Attempt to use the Position property if <c>true</c> and the Length property is not available (only valid if stream is at the end).</param>
/// <returns>Returns the stream's length, if available, or null if not supported by the stream.</returns>
public static long? GetStreamLength ( Stream stream , out bool isStreamPosition , bool allowPositionFallback = true )
{
isStreamPosition = false ;
long? streamLength = null ;
try { streamLength = stream . Length ; } catch { }
if (! streamLength . HasValue && allowPositionFallback )
{
try
{
// Hack: This is a fall-back method to detect the source stream size, assuming the current position is the end of the stream.
streamLength = stream . Position ;
isStreamPosition = true ;
}
catch { } //
}
return streamLength ;
}
2013-02-12 21:43:14 +00:00
/// <summary>
2013-05-13 22:32:05 +02:00
/// These are characters that must be escaped when using a globbing expression
2013-02-12 21:43:14 +00:00
/// </summary>
2018-04-20 17:30:08 +02:00
private static readonly string BADCHARS = @"\\|\+|\||\{|\[|\(|\)|\]|\}|\^|\$|\#|\." ;
2013-02-12 21:43:14 +00:00
/// <summary>
2013-05-13 22:32:05 +02:00
/// Most people will probably want to use fileglobbing, but RegExp's are more flexible.
/// By converting from the weak globbing to the stronger regexp, we support both.
2013-02-12 21:43:14 +00:00
/// </summary>
2013-05-13 22:32:05 +02:00
/// <param name="globexp"></param>
/// <returns></returns>
public static string ConvertGlobbingToRegExp ( string globexp )
2013-02-12 21:43:14 +00:00
{
2013-05-13 22:32:05 +02:00
//First escape all special characters
2018-04-20 17:30:08 +02:00
globexp = Regex . Replace ( globexp , BADCHARS , @"\$&" );
2013-02-12 21:43:14 +00:00
2013-05-13 22:32:05 +02:00
//Replace the globbing expressions with the corresponding regular expressions
globexp = globexp . Replace ( '?' , '.' ). Replace ( "*" , ".*" );
return globexp ;
2013-02-12 21:43:14 +00:00
}
2020-08-30 15:52:53 -07:00
/// <summary>
/// Convert literal path to the equivalent regular expression.
/// </summary>
public static string ConvertLiteralToRegExp ( string literalPath )
{
// Escape all special characters
return Regex . Escape ( literalPath );
}
2013-02-12 21:43:14 +00:00
/// <summary>
/// Returns a list of all files found in the given folder.
/// The search is recursive.
/// </summary>
/// <param name="basepath">The folder to look in</param>
/// <returns>A list of the full filenames</returns>
2013-05-13 22:32:05 +02:00
public static IEnumerable < string > EnumerateFiles ( string basepath )
2013-02-12 21:43:14 +00:00
{
2018-11-18 16:49:38 -08:00
return EnumerateFileSystemEntries ( basepath ). Where ( x => ! x . EndsWith ( Util . DirectorySeparatorString , StringComparison . Ordinal ));
2013-02-12 21:43:14 +00:00
}
/// <summary>
/// Returns a list of folder names found in the given folder.
/// The search is recursive.
/// </summary>
/// <param name="basepath">The folder to look in</param>
/// <returns>A list of the full paths</returns>
2013-05-13 22:32:05 +02:00
public static IEnumerable < string > EnumerateFolders ( string basepath )
2013-02-12 21:43:14 +00:00
{
2018-11-18 16:48:53 -08:00
return EnumerateFileSystemEntries ( basepath ). Where ( x => x . EndsWith ( Util . DirectorySeparatorString , StringComparison . Ordinal ));
2013-02-12 21:43:14 +00:00
}
/// <summary>
/// Returns a list of all files and subfolders found in the given folder.
/// The search is recursive.
/// </summary>
/// <param name="basepath">The folder to look in.</param>
/// <returns>A list of the full filenames and foldernames. Foldernames ends with the directoryseparator char</returns>
2013-05-13 22:32:05 +02:00
public static IEnumerable < string > EnumerateFileSystemEntries ( string basepath )
2013-02-12 21:43:14 +00:00
{
2018-11-18 17:00:46 -08:00
return EnumerateFileSystemEntries ( basepath , ( rootpath , path , attributes ) => true , SystemIO . IO_OS . GetDirectories , Directory . GetFiles , null );
2013-02-12 21:43:14 +00:00
}
/// <summary>
/// A callback delegate used for applying alternate enumeration of filesystems
/// </summary>
/// <param name="path">The path to return data from</param>
/// <returns>A list of paths</returns>
public delegate string [] FileSystemInteraction ( string path );
2013-08-17 22:03:19 +02:00
2013-02-12 21:43:14 +00:00
/// <summary>
/// A callback delegate used for extracting attributes from a file or folder
/// </summary>
/// <param name="path">The path to return data from</param>
/// <returns>Attributes for the file or folder</returns>
2018-04-20 17:30:08 +02:00
public delegate FileAttributes ExtractFileAttributes ( string path );
2013-08-17 22:03:19 +02:00
2017-01-23 20:53:18 +01:00
/// <summary>
/// A callback delegate used for extracting attributes from a file or folder
/// </summary>
/// <param name="rootpath">The root folder where the path was found</param>
/// <param name="path">The path that produced the error</param>
/// <param name="ex">The exception for the error</param>
public delegate void ReportAccessError ( string rootpath , string path , Exception ex );
2013-02-12 21:43:14 +00:00
/// <summary>
/// Returns a list of all files found in the given folder.
/// The search is recursive.
/// </summary>
/// <param name="rootpath">The folder to look in</param>
/// <param name="callback">The function to call with the filenames</param>
/// <param name="folderList">A function to call that lists all folders in the supplied folder</param>
/// <param name="fileList">A function to call that lists all files in the supplied folder</param>
/// <param name="attributeReader">A function to call that obtains the attributes for an element, set to null to avoid reading attributes</param>
2017-01-23 20:53:18 +01:00
/// <param name="errorCallback">An optional function to call with error messages.</param>
2013-02-12 21:43:14 +00:00
/// <returns>A list of the full filenames</returns>
2017-01-23 20:53:18 +01:00
public static IEnumerable < string > EnumerateFileSystemEntries ( string rootpath , EnumerationFilterDelegate callback , FileSystemInteraction folderList , FileSystemInteraction fileList , ExtractFileAttributes attributeReader , ReportAccessError errorCallback = null )
2013-06-29 12:17:52 +02:00
{
2018-04-22 23:03:15 +02:00
var lst = new Stack < string >();
2018-01-04 23:05:01 -06:00
2018-04-19 22:59:28 +02:00
if ( IsFolder ( rootpath , attributeReader ))
2013-08-30 22:10:43 +02:00
{
2018-10-27 12:17:07 +02:00
rootpath = Util . AppendDirSeparator ( rootpath );
2013-08-30 22:10:43 +02:00
try
{
2018-04-22 23:03:15 +02:00
var attr = attributeReader ?. Invoke ( rootpath ) ?? FileAttributes . Directory ;
2013-08-30 22:10:43 +02:00
if ( callback ( rootpath , rootpath , attr ))
lst . Push ( rootpath );
}
catch ( System . Threading . ThreadAbortException )
{
throw ;
}
2017-01-23 20:53:18 +01:00
catch ( Exception ex )
2013-08-30 22:10:43 +02:00
{
2018-04-22 23:03:15 +02:00
errorCallback ?. Invoke ( rootpath , rootpath , ex );
2018-04-20 17:30:08 +02:00
callback ( rootpath , rootpath , FileAttributes . Directory | ATTRIBUTE_ERROR );
2013-08-30 22:10:43 +02:00
}
2013-05-22 21:19:38 +02:00
2013-06-29 12:17:52 +02:00
while ( lst . Count > 0 )
{
2019-01-01 11:25:50 -08:00
var f = lst . Pop ();
2018-01-04 23:05:01 -06:00
2013-08-30 22:10:43 +02:00
yield return f ;
2018-01-04 23:05:01 -06:00
2013-06-29 12:17:52 +02:00
try
{
2018-04-22 23:03:15 +02:00
foreach ( var s in folderList ( f ))
2013-08-30 22:10:43 +02:00
{
2018-10-27 12:17:07 +02:00
var sf = Util . AppendDirSeparator ( s );
2017-01-23 20:53:18 +01:00
try
{
2018-04-22 23:03:15 +02:00
var attr = attributeReader ?. Invoke ( sf ) ?? FileAttributes . Directory ;
2017-01-23 20:53:18 +01:00
if ( callback ( rootpath , sf , attr ))
lst . Push ( sf );
}
catch ( System . Threading . ThreadAbortException )
{
throw ;
}
catch ( Exception ex )
{
2018-04-22 23:03:15 +02:00
errorCallback ?. Invoke ( rootpath , sf , ex );
2018-04-20 17:30:08 +02:00
callback ( rootpath , sf , FileAttributes . Directory | ATTRIBUTE_ERROR );
2017-01-23 20:53:18 +01:00
}
2013-08-30 22:10:43 +02:00
}
2013-06-29 12:17:52 +02:00
}
catch ( System . Threading . ThreadAbortException )
{
throw ;
}
2017-01-23 20:53:18 +01:00
catch ( Exception ex )
2013-06-29 12:17:52 +02:00
{
2018-04-22 23:03:15 +02:00
errorCallback ?. Invoke ( rootpath , f , ex );
2018-04-20 17:30:08 +02:00
callback ( rootpath , f , FileAttributes . Directory | ATTRIBUTE_ERROR );
2013-06-29 12:17:52 +02:00
}
2013-08-30 22:10:43 +02:00
2013-06-29 12:17:52 +02:00
string [] files = null ;
if ( fileList != null )
2018-04-22 23:03:15 +02:00
{
2013-06-29 12:17:52 +02:00
try
{
files = fileList ( f );
}
catch ( System . Threading . ThreadAbortException )
{
throw ;
}
2017-01-23 20:53:18 +01:00
catch ( Exception ex )
2013-06-29 12:17:52 +02:00
{
2018-04-22 23:03:15 +02:00
errorCallback ?. Invoke ( rootpath , f , ex );
2018-04-20 17:30:08 +02:00
callback ( rootpath , f , FileAttributes . Directory | ATTRIBUTE_ERROR );
2013-06-29 12:17:52 +02:00
}
2018-04-22 23:03:15 +02:00
}
2018-01-04 23:05:01 -06:00
2013-06-29 12:17:52 +02:00
if ( files != null )
2018-04-22 23:03:15 +02:00
{
2018-01-04 23:05:01 -06:00
foreach ( var s in files )
2013-06-29 12:17:52 +02:00
{
try
{
2018-04-22 23:03:15 +02:00
var attr = attributeReader ?. Invoke ( s ) ?? FileAttributes . Normal ;
2013-06-29 12:17:52 +02:00
if (! callback ( rootpath , s , attr ))
continue ;
}
catch ( System . Threading . ThreadAbortException )
{
throw ;
}
2017-01-23 20:53:18 +01:00
catch ( Exception ex )
2013-06-29 12:17:52 +02:00
{
2018-04-22 23:03:15 +02:00
errorCallback ?. Invoke ( rootpath , s , ex );
2013-06-29 12:17:52 +02:00
callback ( rootpath , s , ATTRIBUTE_ERROR );
continue ;
}
yield return s ;
}
2018-04-22 23:03:15 +02:00
}
2013-06-29 12:17:52 +02:00
}
}
2013-08-30 22:10:43 +02:00
else
2013-06-29 12:17:52 +02:00
{
try
{
2018-04-22 23:03:15 +02:00
var attr = attributeReader ?. Invoke ( rootpath ) ?? FileAttributes . Normal ;
2013-06-29 12:17:52 +02:00
if (! callback ( rootpath , rootpath , attr ))
yield break ;
}
catch ( System . Threading . ThreadAbortException )
{
throw ;
}
2017-01-23 20:53:18 +01:00
catch ( Exception ex )
2013-06-29 12:17:52 +02:00
{
2018-04-22 23:03:15 +02:00
errorCallback ?. Invoke ( rootpath , rootpath , ex );
2013-06-29 12:17:52 +02:00
callback ( rootpath , rootpath , ATTRIBUTE_ERROR );
yield break ;
}
2018-01-04 23:05:01 -06:00
2013-06-29 12:17:52 +02:00
yield return rootpath ;
}
2013-08-17 22:03:19 +02:00
}
2013-02-12 21:43:14 +00:00
/// <summary>
2018-04-19 22:59:28 +02:00
/// Test if specified path is a folder
/// </summary>
/// <param name="path">Path to test</param>
/// <param name="attributeReader">Function to use for testing path</param>
/// <returns>True if path is refers to a folder</returns>
public static bool IsFolder ( string path , ExtractFileAttributes attributeReader )
{
if ( attributeReader == null )
return true ;
try
{
2018-04-20 17:30:08 +02:00
return attributeReader ( path ). HasFlag ( FileAttributes . Directory );
2018-04-19 22:59:28 +02:00
}
catch
{
return false ;
}
}
/// <summary>
2018-04-20 17:30:08 +02:00
/// Tests if path refers to a file, or folder, <b>below</b> the parent folder
2018-04-19 22:59:28 +02:00
/// </summary>
/// <param name="fileOrFolderPath">File or folder to test</param>
/// <param name="parentFolder">Candidate parent folder</param>
2018-04-20 23:51:26 +02:00
/// <returns>True if below parent folder, false otherwise
/// (note that this returns false if the two argument paths are identical!)</returns>
2018-04-19 22:59:28 +02:00
public static bool IsPathBelowFolder ( string fileOrFolderPath , string parentFolder )
{
2018-10-27 12:17:07 +02:00
var sanitizedParentFolder = Util . AppendDirSeparator ( parentFolder );
2018-05-16 19:40:51 +02:00
return fileOrFolderPath . StartsWith ( sanitizedParentFolder , ClientFilenameStringComparison ) &&
! fileOrFolderPath . Equals ( sanitizedParentFolder , ClientFilenameStringComparison );
2018-04-19 22:59:28 +02:00
}
/// <summary>
/// Returns parent folder of path
/// </summary>
/// <param name="path">Full file or folder path</param>
/// <param name="forceTrailingDirectorySeparator">If true, return value always has trailing separator</param>
/// <returns>Parent folder of path (containing folder for file paths, parent folder for folder paths)</returns>
2018-04-20 09:17:06 +02:00
public static string GetParent ( string path , bool forceTrailingDirectorySeparator )
2018-04-19 22:59:28 +02:00
{
var len = path . Length - 1 ;
if ( len > 1 && path [ len ] == Path . DirectorySeparatorChar )
{
len --;
}
var last = path . LastIndexOf ( Path . DirectorySeparatorChar , len );
if ( last == - 1 || last == 0 && len == 0 )
return null ;
2018-10-27 12:17:07 +02:00
2018-11-02 22:13:25 +01:00
if ( last == 0 && ! Platform . IsClientWindows )
2018-10-27 12:17:07 +02:00
return Util . DirectorySeparatorString ;
2018-04-19 22:59:28 +02:00
var parent = path . Substring ( 0 , last );
if ( forceTrailingDirectorySeparator ||
2018-11-02 22:13:25 +01:00
Platform . IsClientWindows && parent . Length == 2 && parent [ 1 ] == ':' && char . IsLetter ( parent [ 0 ]))
2018-04-19 22:59:28 +02:00
{
parent += Path . DirectorySeparatorChar ;
}
return parent ;
}
/// <summary>
/// Given a collection of unique folders, returns only parent-most folders
/// </summary>
/// <param name="folders">Collection of unique folders</param>
/// <returns>Parent-most folders of input collection</returns>
public static IEnumerable < string > SimplifyFolderList ( ICollection < string > folders )
{
if (! folders . Any ())
return folders ;
var result = new LinkedList < string >();
result . AddFirst ( folders . First ());
foreach ( var folder1 in folders )
{
bool addFolder = true ;
LinkedListNode < string > next ;
for ( var node = result . First ; node != null ; node = next )
{
next = node . Next ;
var folder2 = node . Value ;
if ( IsPathBelowFolder ( folder1 , folder2 ))
{
// higher-level folder already present
addFolder = false ;
break ;
}
if ( IsPathBelowFolder ( folder2 , folder1 ))
{
// retain folder1
result . Remove ( node );
}
}
if ( addFolder )
{
result . AddFirst ( folder1 );
}
}
return result . Distinct ();
}
/// <summary>
/// Given a collection of file paths, return those NOT contained within specified collection of folders
/// </summary>
/// <param name="files">Collection of files to filter</param>
/// <param name="folders">Collection of folders to use as filter</param>
/// <returns>Files not in any of specified <c>folders</c></returns>
public static IEnumerable < string > GetFilesNotInFolders ( IEnumerable < string > files , IEnumerable < string > folders )
{
return files . Where ( x => folders . All ( folder => ! IsPathBelowFolder ( x , folder )));
}
2013-02-12 21:43:14 +00:00
/// <summary>
/// Calculates the size of files in a given folder
/// </summary>
/// <param name="folder">The folder to examine</param>
/// <returns>The combined size of all files that match the filter</returns>
2018-11-18 16:47:59 -08:00
public static long GetDirectorySize ( string folder )
2013-08-17 22:03:19 +02:00
{
2018-11-18 16:47:59 -08:00
return EnumerateFolders ( folder ). Sum (( path ) => new FileInfo ( path ). Length );
2013-08-17 22:03:19 +02:00
}
2013-02-12 21:43:14 +00:00
/// <summary>
/// Some streams can return a number that is less than the requested number of bytes.
/// This is usually due to fragmentation, and is solved by issuing a new read.
/// This function wraps that functionality.
/// </summary>
/// <param name="stream">The stream to read</param>
/// <param name="buf">The buffer to read into</param>
2019-11-30 11:35:43 -08:00
/// <param name="count">The amount of bytes to read</param>
2013-02-12 21:43:14 +00:00
/// <returns>The actual number of bytes read</returns>
2018-04-20 17:30:08 +02:00
public static int ForceStreamRead ( Stream stream , byte [] buf , int count )
2013-08-17 22:03:19 +02:00
{
int a ;
int index = 0 ;
do
{
a = stream . Read ( buf , index , count );
index += a ;
count -= a ;
} while ( a != 0 && count > 0 );
return index ;
}
2016-02-09 09:17:31 +01:00
/// <summary>
/// Some streams can return a number that is less than the requested number of bytes.
/// This is usually due to fragmentation, and is solved by issuing a new read.
/// This function wraps that functionality.
/// </summary>
/// <param name="stream">The stream to read.</param>
/// <param name="buf">The buffer to read into.</param>
2019-11-30 11:35:43 -08:00
/// <param name="count">The amount of bytes to read.</param>
2016-02-09 09:17:31 +01:00
/// <returns>The number of bytes read</returns>
public static async Task < int > ForceStreamReadAsync ( this System . IO . Stream stream , byte [] buf , int count )
{
int a ;
int index = 0 ;
do
{
2018-06-20 20:11:21 -07:00
a = await stream . ReadAsync ( buf , index , count ). ConfigureAwait ( false );
2016-02-09 09:17:31 +01:00
index += a ;
count -= a ;
} while ( a != 0 && count > 0 );
return index ;
}
2013-02-12 21:43:14 +00:00
/// <summary>
/// Compares two streams to see if they are binary equals
/// </summary>
/// <param name="stream1">One stream</param>
/// <param name="stream2">Another stream</param>
/// <param name="checkLength">True if the length of the two streams should be compared</param>
/// <returns>True if they are equal, false otherwise</returns>
2018-04-20 17:30:08 +02:00
public static bool CompareStreams ( Stream stream1 , Stream stream2 , bool checkLength )
2013-08-17 22:03:19 +02:00
{
if ( checkLength )
{
try
{
if ( stream1 . Length != stream2 . Length )
return false ;
}
catch
{
//We must read along, trying to determine if they are equals
}
}
int longSize = BitConverter . GetBytes (( long ) 0 ). Length ;
byte [] buf1 = new byte [ longSize * 512 ];
byte [] buf2 = new byte [ buf1 . Length ];
int a1 , a2 ;
while (( a1 = ForceStreamRead ( stream1 , buf1 , buf1 . Length )) == ( a2 = ForceStreamRead ( stream2 , buf2 , buf2 . Length )))
{
int ix = 0 ;
2018-01-04 23:05:01 -06:00
for ( int i = 0 ; i < a1 / longSize ; i ++)
2013-08-17 22:03:19 +02:00
if ( BitConverter . ToUInt64 ( buf1 , ix ) != BitConverter . ToUInt64 ( buf2 , ix ))
return false ;
else
ix += longSize ;
2018-01-04 23:05:01 -06:00
for ( int i = 0 ; i < a1 % longSize ; i ++)
2013-08-17 22:03:19 +02:00
if ( buf1 [ ix ] != buf2 [ ix ])
return false ;
else
ix ++;
if ( a1 == 0 )
break ;
}
return a1 == a2 ;
}
2013-02-12 21:43:14 +00:00
/// <summary>
/// Reads a file, attempts to detect encoding
/// </summary>
/// <param name="filename">The path to the file to read</param>
/// <returns>The file contents</returns>
2013-08-17 22:03:19 +02:00
public static string ReadFileWithDefaultEncoding ( string filename )
{
// Since StreamReader defaults to UTF8 and most text files will NOT be UTF8 without BOM,
// we need to detect the encoding (at least that it's not UTF8).
// So we read the first 4096 bytes and try to decode them as UTF8.
2018-04-20 17:30:08 +02:00
var buffer = new byte [ 4096 ];
using ( var file = new FileStream ( filename , FileMode . Open , FileAccess . Read , FileShare . Read ))
2018-05-30 17:42:59 -07:00
{
Utility . ForceStreamRead ( file , buffer , 4096 );
}
2013-08-17 22:03:19 +02:00
2018-04-20 17:30:08 +02:00
var enc = Encoding . UTF8 ;
2013-08-17 22:03:19 +02:00
try
{
// this will throw an error if not really UTF8
2018-04-20 17:30:08 +02:00
// ReSharper disable once ReturnValueOfPureMethodIsNotUsed
2018-01-04 23:05:01 -06:00
new UTF8Encoding ( false , true ). GetString ( buffer );
2013-08-17 22:03:19 +02:00
}
catch ( Exception )
{
enc = Encoding . Default ;
}
// This will load the text using the BOM, or the detected encoding if no BOM.
2018-04-20 17:30:08 +02:00
using ( var reader = new StreamReader ( filename , enc , true ))
2013-08-17 22:03:19 +02:00
{
// Remove all \r from the file and split on \n, then pass directly to ExtractOptions
return reader . ReadToEnd ();
}
}
2013-02-12 21:43:14 +00:00
/// <summary>
2018-04-20 17:30:08 +02:00
/// Formats a size into a human readable format, eg. 2048 becomes "2 KB" or -2283 becomes "-2.23 KB%quot.
2013-02-12 21:43:14 +00:00
/// </summary>
/// <param name="size">The size to format</param>
/// <returns>A human readable string representing the size</returns>
2018-01-30 23:46:36 +01:00
public static string FormatSizeString ( double size )
2013-08-17 22:03:19 +02:00
{
2018-01-30 23:46:36 +01:00
double sizeAbs = Math . Abs ( size ); // Allow formatting of negative sizes
2018-01-04 23:05:01 -06:00
if ( sizeAbs >= 1024 * 1024 * 1024 * 1024L )
2018-01-30 23:46:36 +01:00
return Strings . Utility . FormatStringTB ( size / ( 1024 * 1024 * 1024 * 1024L ));
2018-01-04 23:05:01 -06:00
else if ( sizeAbs >= 1024 * 1024 * 1024 )
2018-01-30 23:46:36 +01:00
return Strings . Utility . FormatStringGB ( size / ( 1024 * 1024 * 1024 ));
2018-01-04 23:05:01 -06:00
else if ( sizeAbs >= 1024 * 1024 )
2018-01-30 23:46:36 +01:00
return Strings . Utility . FormatStringMB ( size / ( 1024 * 1024 ));
2018-01-04 23:05:01 -06:00
else if ( sizeAbs >= 1024 )
2018-01-30 23:46:36 +01:00
return Strings . Utility . FormatStringKB ( size / 1024 );
2013-08-17 22:03:19 +02:00
else
2018-01-30 23:46:36 +01:00
return Strings . Utility . FormatStringB (( long ) size ); // safe to cast because lower than 1024 and thus well within range of long
2013-08-17 22:03:19 +02:00
}
public static System . Threading . ThreadPriority ParsePriority ( string value )
{
if ( string . IsNullOrEmpty ( value ) || value . Trim (). Length == 0 )
return System . Threading . ThreadPriority . Normal ;
2018-12-11 21:07:30 -08:00
switch ( value . ToLower ( CultureInfo . InvariantCulture ). Trim ())
2013-08-17 22:03:19 +02:00
{
case "+2" :
case "high" :
case "highest" :
return System . Threading . ThreadPriority . Highest ;
case "+1" :
case "abovenormal" :
case "above normal" :
return System . Threading . ThreadPriority . AboveNormal ;
case "-1" :
case "belownormal" :
case "below normal" :
return System . Threading . ThreadPriority . BelowNormal ;
case "-2" :
case "low" :
case "lowest" :
case "idle" :
return System . Threading . ThreadPriority . Lowest ;
default :
return System . Threading . ThreadPriority . Normal ;
}
}
2013-02-12 21:43:14 +00:00
/// <summary>
2017-12-17 14:05:19 -08:00
/// Parses a string into a boolean value.
2013-02-12 21:43:14 +00:00
/// </summary>
2017-12-17 14:05:19 -08:00
/// <param name="value">The value to parse.</param>
/// <param name="defaultFunc">A delegate that returns the default value if <paramref name="value"/> is not a valid boolean value.</param>
/// <returns>The parsed value, or the value returned by <paramref name="defaultFunc"/>.</returns>
public static bool ParseBool ( string value , Func < bool > defaultFunc )
2013-08-17 22:03:19 +02:00
{
2017-12-17 14:05:19 -08:00
if ( String . IsNullOrWhiteSpace ( value ))
{
return defaultFunc ();
}
2013-08-17 22:03:19 +02:00
2018-12-11 21:07:30 -08:00
switch ( value . Trim (). ToLower ( CultureInfo . InvariantCulture ))
2013-08-17 22:03:19 +02:00
{
case "1" :
case "on" :
case "true" :
case "yes" :
return true ;
case "0" :
case "off" :
case "false" :
case "no" :
return false ;
default :
2017-12-17 14:05:19 -08:00
return defaultFunc ();
2013-08-17 22:03:19 +02:00
}
}
2017-12-17 14:05:19 -08:00
/// <summary>
/// Parses a string into a boolean value.
/// </summary>
/// <param name="value">The value to parse.</param>
/// <param name="default">The default value, in case <paramref name="value"/> is not a valid boolean value.</param>
/// <returns>The parsed value, or the default value.</returns>
public static bool ParseBool ( string value , bool @default )
{
2018-04-20 17:30:08 +02:00
return ParseBool ( value , () => @default );
2017-12-17 14:05:19 -08:00
}
2013-02-12 21:43:14 +00:00
/// <summary>
/// Parses an option from the option set, using the convention that if the option is set, it is true unless it parses to false, and false otherwise
/// </summary>
/// <param name="options">The set of options to look for the setting in</param>
/// <param name="value">The value to look for in the settings</param>
/// <returns></returns>
2013-08-17 22:03:19 +02:00
public static bool ParseBoolOption ( IDictionary < string , string > options , string value )
{
string opt ;
if ( options . TryGetValue ( value , out opt ))
return ParseBool ( opt , true );
else
return false ;
2018-04-02 11:20:46 +02:00
}
2018-04-10 12:58:06 +02:00
/// <summary>
/// Parses an enum found in the options dictionary
/// </summary>
/// <returns>The parsed or default enum value.</returns>
/// <param name="options">The set of options to look for the setting in</param>
/// <param name="value">The value to look for in the settings</param>
/// <param name="default">The default value to return if there are no matches.</param>
/// <typeparam name="T">The enum type parameter.</typeparam>
public static T ParseEnumOption < T >( IDictionary < string , string > options , string value , T @default )
{
2018-04-20 17:30:08 +02:00
return options . TryGetValue ( value , out var opt ) ? ParseEnum ( opt , @default ) : @default ;
2013-08-17 22:03:19 +02:00
}
2013-02-12 21:43:14 +00:00
2018-04-10 12:58:06 +02:00
/// <summary>
/// Attempts to parse an enum with case-insensitive lookup, returning the default value if there was no match
/// </summary>
/// <returns>The parsed or default enum value.</returns>
/// <param name="value">The string to parse.</param>
/// <param name="default">The default value to return if there are no matches.</param>
/// <typeparam name="T">The enum type parameter.</typeparam>
2018-04-02 11:20:46 +02:00
public static T ParseEnum < T >( string value , T @default )
{
2018-04-20 17:30:08 +02:00
foreach ( var s in Enum . GetNames ( typeof ( T )))
2018-04-02 11:20:46 +02:00
if ( s . Equals ( value , StringComparison . OrdinalIgnoreCase ))
return ( T ) Enum . Parse ( typeof ( T ), s );
2013-08-17 22:03:19 +02:00
2018-04-02 11:20:46 +02:00
return @default ;
2013-08-17 22:03:19 +02:00
}
2013-02-12 21:43:14 +00:00
/// <summary>
2014-08-13 23:42:26 +02:00
/// Converts a sequence of bytes to a hex string
2013-02-12 21:43:14 +00:00
/// </summary>
2014-08-13 23:42:26 +02:00
/// <returns>The array as hex string.</returns>
2013-02-12 21:43:14 +00:00
/// <param name="data">The data to convert</param>
2013-08-17 22:03:19 +02:00
public static string ByteArrayAsHexString ( byte [] data )
{
2014-08-13 23:42:26 +02:00
return BitConverter . ToString ( data ). Replace ( "-" , string . Empty );
2013-08-17 22:03:19 +02:00
}
2013-02-12 21:43:14 +00:00
/// <summary>
2014-08-13 23:42:26 +02:00
/// Converts a hex string to a byte array
2013-02-12 21:43:14 +00:00
/// </summary>
2014-08-13 23:42:26 +02:00
/// <returns>The string as byte array.</returns>
2013-02-12 21:43:14 +00:00
/// <param name="hex">The hex string</param>
2014-08-13 23:42:26 +02:00
/// <param name="data">The parsed data</param>
2019-09-14 18:46:06 -07:00
public static void HexStringAsByteArray ( string hex , byte [] data )
2013-08-17 22:03:19 +02:00
{
2014-08-13 23:42:26 +02:00
for ( var i = 0 ; i < hex . Length ; i += 2 )
data [ i / 2 ] = Convert . ToByte ( hex . Substring ( i , 2 ), 16 );
2014-03-27 15:24:38 +01:00
}
2014-08-13 23:42:26 +02:00
2014-03-27 15:24:38 +01:00
public static bool Which ( string appname )
{
2018-11-03 09:26:04 +01:00
if (! Platform . IsClientPosix )
2014-03-27 15:24:38 +01:00
return false ;
2018-01-04 23:05:01 -06:00
2014-03-27 15:24:38 +01:00
try
{
2019-09-01 09:47:04 -07:00
var psi = new System . Diagnostics . ProcessStartInfo ( "which" , appname )
{
2018-06-12 09:33:09 +02:00
RedirectStandardOutput = true ,
RedirectStandardError = false ,
2019-09-01 09:47:04 -07:00
RedirectStandardInput = false ,
UseShellExecute = false
2018-06-12 09:33:09 +02:00
};
2018-01-04 23:05:01 -06:00
2014-03-27 15:24:38 +01:00
var pi = System . Diagnostics . Process . Start ( psi );
pi . WaitForExit ( 5000 );
if ( pi . HasExited )
return pi . ExitCode == 0 ;
else
return false ;
}
catch
{
}
2018-01-04 23:05:01 -06:00
2014-03-27 15:24:38 +01:00
return false ;
}
2013-08-17 22:03:19 +02:00
2016-04-21 10:57:46 +02:00
2013-08-17 22:03:19 +02:00
/// <value>
/// Returns a value indicating if the filesystem, is case sensitive
/// </value>
public static bool IsFSCaseSensitive
{
get
2017-09-13 23:23:15 -07:00
{
2018-04-10 17:39:57 -07:00
if (! CachedIsFSCaseSensitive . HasValue )
{
var str = Environment . GetEnvironmentVariable ( "FILESYSTEM_CASE_SENSITIVE" );
2017-06-14 22:50:15 +02:00
2018-04-10 17:39:57 -07:00
// TODO: This should probably be determined by filesystem rather than OS,
2019-11-30 11:35:43 -08:00
// OSX can actually have the disks formatted as Case Sensitive, but insensitive is default
2018-11-03 09:26:04 +01:00
CachedIsFSCaseSensitive = ParseBool ( str , () => Platform . IsClientPosix && ! Platform . IsClientOSX );
2018-04-10 17:39:57 -07:00
}
2017-12-17 14:11:16 -08:00
2018-04-10 17:39:57 -07:00
return CachedIsFSCaseSensitive . Value ;
2013-08-17 22:03:19 +02:00
}
}
2013-02-12 21:43:14 +00:00
/// <summary>
/// Gets a string comparer that matches the client filesystems case sensitivity
/// </summary>
2018-08-18 20:25:43 -07:00
public static StringComparer ClientFilenameStringComparer => IsFSCaseSensitive ? StringComparer . Ordinal : StringComparer . OrdinalIgnoreCase ;
2013-08-17 22:03:19 +02:00
2013-02-12 21:43:14 +00:00
/// <summary>
/// Gets the string comparision that matches the client filesystems case sensitivity
/// </summary>
2018-08-18 20:25:43 -07:00
public static StringComparison ClientFilenameStringComparison => IsFSCaseSensitive ? StringComparison . Ordinal : StringComparison . OrdinalIgnoreCase ;
2013-08-17 22:03:19 +02:00
2015-09-05 09:14:21 +02:00
/// <summary>
/// The path to the users home directory
/// </summary>
2018-11-03 09:26:04 +01:00
public static readonly string HOME_PATH = Environment . GetFolderPath ( Platform . IsClientPosix ? Environment . SpecialFolder . Personal : Environment . SpecialFolder . UserProfile );
2015-09-05 09:14:21 +02:00
2015-11-28 21:55:22 +01:00
/// <summary>
/// Regexp for matching environment variables on Windows (%VAR%)
/// </summary>
private static readonly Regex ENVIRONMENT_VARIABLE_MATCHER_WINDOWS = new Regex ( @"\%(?<name>\w+)\%" );
/// <summary>
2017-10-21 09:21:07 -07:00
/// Expands environment variables in a RegExp safe format
2015-11-28 21:55:22 +01:00
/// </summary>
/// <returns>The expanded string.</returns>
/// <param name="str">The string to expand.</param>
2016-06-22 00:42:48 +02:00
/// <param name="lookup">A lookup method that converts an environment key to an expanded string</param>
public static string ExpandEnvironmentVariablesRegexp ( string str , Func < string , string > lookup = null )
2015-11-28 21:55:22 +01:00
{
2016-06-22 00:42:48 +02:00
if ( lookup == null )
2018-04-20 17:30:08 +02:00
lookup = Environment . GetEnvironmentVariable ;
2016-06-22 00:42:48 +02:00
2015-11-28 21:55:22 +01:00
return
2018-08-02 21:40:51 -07:00
// TODO: Should we switch to using the native format ($VAR or ${VAR}), instead of following the Windows scheme?
// IsClientLinux ? new Regex(@"\$(?<name>\w+)|(\{(?<name>[^\}]+)\})") : ENVIRONMENT_VARIABLE_MATCHER_WINDOWS
2015-11-28 21:55:22 +01:00
2018-04-20 17:30:08 +02:00
ENVIRONMENT_VARIABLE_MATCHER_WINDOWS . Replace ( str , m => Regex . Escape ( lookup ( m . Groups [ "name" ]. Value )));
2015-11-28 21:55:22 +01:00
}
2019-09-01 09:47:04 -07:00
/// <summary>
2019-09-01 10:01:50 -07:00
/// Normalizes a DateTime instance by converting to UTC and flooring to seconds.
2019-09-01 09:47:04 -07:00
/// </summary>
2019-09-01 10:01:50 -07:00
/// <returns>The normalized date time</returns>
2019-09-01 09:47:04 -07:00
/// <param name="input">The input time</param>
public static DateTime NormalizeDateTime ( DateTime input )
{
var ticks = input . ToUniversalTime (). Ticks ;
ticks -= ticks % TimeSpan . TicksPerSecond ;
return new DateTime ( ticks , DateTimeKind . Utc );
}
2019-09-20 19:08:26 -07:00
/// <summary>
/// Given a DateTime instance, return the number of elapsed seconds since the Unix epoch
/// </summary>
/// <returns>The number of elapsed seconds since the Unix epoch</returns>
/// <param name="input">The input time</param>
2019-09-01 09:56:14 -07:00
public static long NormalizeDateTimeToEpochSeconds ( DateTime input )
{
2019-10-23 19:45:45 -07:00
// Note that we cannot return (new DateTimeOffset(input)).ToUnixTimeSeconds() here.
// The DateTimeOffset constructor will convert the provided DateTime to the UTC
// equivalent. However, if DateTime.MinValue is provided (for example, when creating
// a new backup), this can result in values that fall outside the DateTimeOffset.MinValue
// and DateTimeOffset.MaxValue bounds.
return ( long ) Math . Floor (( NormalizeDateTime ( input ) - EPOCH ). TotalSeconds );
2019-09-01 09:56:14 -07:00
}
2015-09-08 16:41:16 +02:00
/// <summary>
/// The format string for a DateTime
/// </summary>
//Note: Actually the K should be Z which is more correct as it is forced to be Z, but Z as a format specifier is fairly undocumented
public static string SERIALIZED_DATE_TIME_FORMAT = "yyyyMMdd'T'HHmmssK" ;
2013-02-12 21:43:14 +00:00
/// <summary>
/// Returns a string representation of a <see cref="System.DateTime"/> in UTC format
/// </summary>
/// <param name="dt">The <see cref="System.DateTime"/> instance</param>
/// <returns>A string representing the time</returns>
2013-08-17 22:03:19 +02:00
public static string SerializeDateTime ( DateTime dt )
{
2015-09-08 16:41:16 +02:00
return dt . ToUniversalTime (). ToString ( SERIALIZED_DATE_TIME_FORMAT , System . Globalization . CultureInfo . InvariantCulture );
}
/// <summary>
/// Parses a serialized <see cref="System.DateTime"/> instance
/// </summary>
/// <param name="str">The string to parse</param>
/// <returns>The parsed <see cref="System.DateTime"/> instance</returns>
public static bool TryDeserializeDateTime ( string str , out DateTime dt )
{
return DateTime . TryParseExact ( str , SERIALIZED_DATE_TIME_FORMAT , System . Globalization . CultureInfo . InvariantCulture , System . Globalization . DateTimeStyles . AssumeUniversal , out dt );
2013-08-17 22:03:19 +02:00
}
2013-02-12 21:43:14 +00:00
/// <summary>
/// Parses a serialized <see cref="System.DateTime"/> instance
/// </summary>
/// <param name="str">The string to parse</param>
/// <returns>The parsed <see cref="System.DateTime"/> instance</returns>
2013-08-17 22:03:19 +02:00
public static DateTime DeserializeDateTime ( string str )
{
2018-04-20 17:30:08 +02:00
if (! TryDeserializeDateTime ( str , out var dt ))
2015-01-20 21:07:24 +01:00
throw new Exception ( Strings . Utility . InvalidDateError ( str ));
2013-08-17 22:03:19 +02:00
return dt ;
}
2013-02-12 21:43:14 +00:00
2017-09-28 20:11:55 -07:00
/// <summary>
/// Gets the unique items from a collection.
/// </summary>
/// <typeparam name="T">The type of the elements in <paramref name="collection"/>.</typeparam>
/// <param name="collection">The collection to remove duplicate items from.</param>
/// <param name="duplicateItems">The duplicate items in <paramref name="collection"/>.</param>
/// <returns>The unique items from <paramref name="collection"/>.</returns>
public static ISet < T > GetUniqueItems < T >( IEnumerable < T > collection , out ISet < T > duplicateItems )
{
2018-04-20 17:30:08 +02:00
return GetUniqueItems ( collection , EqualityComparer < T >. Default , out duplicateItems );
2017-09-28 20:49:44 -07:00
}
/// <summary>
/// Gets the unique items from a collection.
/// </summary>
/// <typeparam name="T">The type of the elements in <paramref name="collection"/>.</typeparam>
/// <param name="collection">The collection to remove duplicate items from.</param>
/// <param name="comparer">The <see cref="System.Collections.Generic.IEqualityComparer{T}"/> implementation to use when comparing values in the collection.</param>
/// <param name="duplicateItems">The duplicate items in <paramref name="collection"/>.</param>
/// <returns>The unique items from <paramref name="collection"/>.</returns>
public static ISet < T > GetUniqueItems < T >( IEnumerable < T > collection , IEqualityComparer < T > comparer , out ISet < T > duplicateItems )
{
2018-04-20 17:30:08 +02:00
var uniqueItems = new HashSet < T >( comparer );
2017-09-28 20:49:44 -07:00
duplicateItems = new HashSet < T >( comparer );
2017-09-28 20:11:55 -07:00
2018-04-20 17:30:08 +02:00
foreach ( var item in collection )
{
2017-09-28 20:11:55 -07:00
if (! uniqueItems . Add ( item ))
duplicateItems . Add ( item );
2018-04-20 17:30:08 +02:00
}
2017-09-28 20:11:55 -07:00
return uniqueItems ;
}
2013-02-12 21:43:14 +00:00
// <summary>
// Returns the entry assembly or reasonable approximation if no entry assembly is available.
// This is the case in NUnit tests. The following approach does not work w/ Mono due to unimplemented members:
// http://social.msdn.microsoft.com/Forums/nb-NO/clr/thread/db44fe1a-3bb4-41d4-a0e0-f3021f30e56f
// so this layer of indirection is necessary
// </summary>
// <returns>entry assembly or reasonable approximation</returns>
2013-08-17 22:03:19 +02:00
public static System . Reflection . Assembly getEntryAssembly ()
{
return System . Reflection . Assembly . GetEntryAssembly () ?? System . Reflection . Assembly . GetExecutingAssembly ();
}
2013-03-08 22:24:54 +01:00
/// <summary>
/// Converts a Base64 encoded string to "base64 for url"
/// See https://en.wikipedia.org/wiki/Base64#URL_applications
/// </summary>
/// <param name="data">The base64 encoded string</param>
/// <returns>The base64 for url encoded string</returns>
2013-08-17 22:03:19 +02:00
public static string Base64PlainToBase64Url ( string data )
{
return data . Replace ( '+' , '-' ). Replace ( '/' , '_' );
}
2013-03-08 22:24:54 +01:00
/// <summary>
/// Converts a "base64 for url" encoded string to a Base64 encoded string.
/// See https://en.wikipedia.org/wiki/Base64#URL_applications
/// </summary>
/// <param name="data">The base64 for url encoded string</param>
/// <returns>The base64 encoded string</returns>
2013-08-17 22:03:19 +02:00
public static string Base64UrlToBase64Plain ( string data )
{
return data . Replace ( '-' , '+' ). Replace ( '_' , '/' );
}
2013-03-08 22:24:54 +01:00
/// <summary>
/// Encodes a byte array into a "base64 for url" encoded string.
/// See https://en.wikipedia.org/wiki/Base64#URL_applications
/// </summary>
/// <param name="data">The data to encode</param>
/// <returns>The base64 for url encoded string</returns>
2013-08-17 22:03:19 +02:00
public static string Base64UrlEncode ( byte [] data )
{
return Base64PlainToBase64Url ( Convert . ToBase64String ( data ));
}
2017-11-08 22:18:25 +01:00
/// <summary>
/// Converts a DateTime instance to a Unix timestamp
/// </summary>
/// <returns>The Unix timestamp.</returns>
/// <param name="input">The DateTime instance to convert.</param>
public static long ToUnixTimestamp ( DateTime input )
{
var ticks = input . ToUniversalTime (). Ticks ;
ticks -= ticks % TimeSpan . TicksPerSecond ;
input = new DateTime ( ticks , DateTimeKind . Utc );
return ( long ) Math . Floor (( input - EPOCH ). TotalSeconds );
}
2016-12-02 11:54:20 +01:00
/// <summary>
/// Returns a value indicating if the given type should be treated as a primitive
/// </summary>
/// <returns><c>true</c>, if type is primitive for serialization, <c>false</c> otherwise.</returns>
/// <param name="t">The type to check.</param>
private static bool IsPrimitiveTypeForSerialization ( Type t )
{
return t . IsPrimitive || t . IsEnum || t == typeof ( string ) || t == typeof ( DateTime ) || t == typeof ( TimeSpan );
}
/// <summary>
/// Writes a primitive to the output, or returns false if the input is not primitive
/// </summary>
/// <returns><c>true</c>, the item was printed, <c>false</c> otherwise.</returns>
/// <param name="item">The item to write.</param>
/// <param name="writer">The target writer.</param>
2018-04-20 17:30:08 +02:00
private static bool PrintSerializeIfPrimitive ( object item , TextWriter writer )
2016-12-02 11:54:20 +01:00
{
if ( item == null )
{
writer . Write ( "null" );
return true ;
}
if ( IsPrimitiveTypeForSerialization ( item . GetType ()))
{
2019-09-29 20:16:28 -07:00
if ( item is DateTime time )
2017-11-08 22:18:25 +01:00
{
2019-09-29 20:16:28 -07:00
writer . Write ( time . ToLocalTime ());
2017-11-08 22:18:25 +01:00
writer . Write ( " (" );
2019-09-29 20:16:28 -07:00
writer . Write ( ToUnixTimestamp ( time ));
2017-11-08 22:18:25 +01:00
writer . Write ( ")" );
}
2017-10-31 10:55:35 +01:00
else
writer . Write ( item );
2016-12-02 11:54:20 +01:00
return true ;
}
return false ;
}
2013-05-22 21:19:38 +02:00
/// <summary>
/// Prints the object to a stream, which can be used for display or logging
/// </summary>
/// <returns>The serialized object</returns>
/// <param name="item">The object to serialize</param>
2016-09-28 20:15:40 +02:00
/// <param name="writer">The writer to write the results to</param>
/// <param name="filter">A filter applied to properties to decide if they are omitted or not</param>
/// <param name="recurseobjects">A value indicating if non-primitive values are recursed</param>
/// <param name="indentation">The string indentation</param>
2019-11-30 11:35:43 -08:00
/// <param name="visited">A lookup table with visited objects, used to avoid infinite recursion</param>
2016-09-28 20:15:40 +02:00
/// <param name="collectionlimit">The maximum number of items to report from an IEnumerable instance</param>
2018-04-20 17:30:08 +02:00
public static void PrintSerializeObject ( object item , TextWriter writer , Func < System . Reflection . PropertyInfo , object , bool > filter = null , bool recurseobjects = false , int indentation = 0 , int collectionlimit = 0 , Dictionary < object , object > visited = null )
2018-01-04 23:05:01 -06:00
{
2016-09-28 20:15:40 +02:00
visited = visited ?? new Dictionary < object , object >();
var indentstring = new string ( ' ' , indentation );
2016-12-02 11:54:20 +01:00
var first = true ;
if ( item == null || IsPrimitiveTypeForSerialization ( item . GetType ()))
{
writer . Write ( indentstring );
if ( PrintSerializeIfPrimitive ( item , writer ))
return ;
}
2016-09-28 20:15:40 +02:00
foreach ( var p in item . GetType (). GetProperties ())
2013-05-25 16:40:15 +02:00
{
2016-12-02 11:54:20 +01:00
if ( filter != null && ! filter ( p , item ))
2013-05-25 16:40:15 +02:00
continue ;
2016-12-02 11:54:20 +01:00
if ( IsPrimitiveTypeForSerialization ( p . PropertyType ))
2013-05-25 16:40:15 +02:00
{
2016-12-02 11:54:20 +01:00
if ( first )
first = false ;
else
writer . WriteLine ();
writer . Write ( "{0}{1}: " , indentstring , p . Name );
PrintSerializeIfPrimitive ( p . GetValue ( item , null ), writer );
2013-05-25 16:40:15 +02:00
}
2018-08-08 11:59:20 +02:00
else if ( typeof ( Task ). IsAssignableFrom ( p . PropertyType ) || p . Name == "TaskReader" )
{
// Ignore Task items
continue ;
}
2013-05-25 16:40:15 +02:00
else if ( typeof ( System . Collections . IEnumerable ). IsAssignableFrom ( p . PropertyType ))
{
var enumerable = ( System . Collections . IEnumerable ) p . GetValue ( item , null );
2016-12-02 11:54:20 +01:00
var any = false ;
2013-05-25 16:40:15 +02:00
if ( enumerable != null )
{
var enumerator = enumerable . GetEnumerator ();
if ( enumerator != null )
{
2016-09-28 20:15:40 +02:00
var remain = collectionlimit ;
2016-12-02 11:54:20 +01:00
if ( first )
first = false ;
else
writer . WriteLine ();
2016-09-28 20:15:40 +02:00
writer . Write ( "{0}{1}: [" , indentstring , p . Name );
2013-05-25 16:40:15 +02:00
if ( enumerator . MoveNext ())
{
2016-12-02 11:54:20 +01:00
any = true ;
2016-09-28 20:15:40 +02:00
writer . WriteLine ();
2016-12-02 11:54:20 +01:00
PrintSerializeObject ( enumerator . Current , writer , filter , recurseobjects , indentation + 4 , collectionlimit , visited );
2016-09-28 20:15:40 +02:00
remain --;
2013-05-25 16:40:15 +02:00
while ( enumerator . MoveNext ())
{
2016-09-28 20:15:40 +02:00
writer . WriteLine ( "," );
if ( remain == 0 )
{
writer . Write ( "..." );
break ;
}
2016-12-02 11:54:20 +01:00
PrintSerializeObject ( enumerator . Current , writer , filter , recurseobjects , indentation + 4 , collectionlimit , visited );
2016-09-28 20:15:40 +02:00
remain --;
2013-05-25 16:40:15 +02:00
}
2016-09-28 20:15:40 +02:00
2016-12-02 11:54:20 +01:00
}
2016-09-28 20:15:40 +02:00
2016-12-02 11:54:20 +01:00
if ( any )
{
writer . WriteLine ();
2016-09-28 20:15:40 +02:00
writer . Write ( indentstring );
2013-05-25 16:40:15 +02:00
}
2016-12-02 11:54:20 +01:00
writer . Write ( "]" );
2013-05-25 16:40:15 +02:00
}
}
}
2016-09-28 20:15:40 +02:00
else if ( recurseobjects )
{
var value = p . GetValue ( item , null );
if ( value == null )
2016-12-02 11:54:20 +01:00
{
if ( first )
first = false ;
else
writer . WriteLine ();
writer . Write ( "{0}{1}: null" , indentstring , p . Name );
}
2016-09-28 20:15:40 +02:00
else if (! visited . ContainsKey ( value ))
{
2016-12-02 11:54:20 +01:00
if ( first )
first = false ;
else
writer . WriteLine ();
2016-09-28 20:15:40 +02:00
writer . WriteLine ( "{0}{1}:" , indentstring , p . Name );
visited [ value ] = null ;
PrintSerializeObject ( value , writer , filter , recurseobjects , indentation + 4 , collectionlimit , visited );
}
}
2013-05-25 16:40:15 +02:00
}
2013-08-17 22:03:19 +02:00
writer . Flush ();
}
2013-05-22 21:19:38 +02:00
/// <summary>
/// Returns a string representing the object, which can be used for display or logging
/// </summary>
/// <returns>The serialized object</returns>
/// <param name="item">The object to serialize</param>
2016-09-28 20:15:40 +02:00
/// <param name="filter">A filter applied to properties to decide if they are omitted or not</param>
/// <param name="recurseobjects">A value indicating if non-primitive values are recursed</param>
/// <param name="indentation">The string indentation</param>
/// <param name="collectionlimit">The maximum number of items to report from an IEnumerable instance, set to zero or less for reporting all</param>
2016-12-02 11:54:20 +01:00
public static StringBuilder PrintSerializeObject ( object item , StringBuilder sb = null , Func < System . Reflection . PropertyInfo , object , bool > filter = null , bool recurseobjects = false , int indentation = 0 , int collectionlimit = 10 )
2013-08-17 22:03:19 +02:00
{
sb = sb ?? new StringBuilder ();
2018-04-20 17:30:08 +02:00
using ( var sw = new StringWriter ( sb ))
2016-09-28 20:15:40 +02:00
PrintSerializeObject ( item , sw , filter , recurseobjects , indentation , collectionlimit );
2013-08-17 22:03:19 +02:00
return sb ;
}
2013-08-17 22:05:40 +02:00
/// <summary>
/// Repeatedly hash a value with a salt.
/// This effectively masks the original value,
/// and destroys lookup methods, like rainbow tables
/// </summary>
/// <param name="data">The data to hash</param>
/// <param name="salt">The salt to apply</param>
/// <param name="repeats">The number of times to repeat the hashing</param>
/// <returns>The salted hash</returns>
public static byte [] RepeatedHashWithSalt ( string data , string salt , int repeats = 1200 )
{
return RepeatedHashWithSalt (
2018-04-20 17:30:08 +02:00
Encoding . UTF8 . GetBytes ( data ?? "" ),
Encoding . UTF8 . GetBytes ( salt ?? "" ),
2013-08-17 22:05:40 +02:00
repeats );
}
2018-01-04 23:05:01 -06:00
2013-08-17 22:05:40 +02:00
/// <summary>
/// Repeatedly hash a value with a salt.
/// This effectively masks the original value,
/// and destroys lookup methods, like rainbow tables
/// </summary>
/// <param name="data">The data to hash</param>
/// <param name="salt">The salt to apply</param>
/// <returns>The salted hash</returns>
public static byte [] RepeatedHashWithSalt ( byte [] data , byte [] salt , int repeats = 1200 )
{
// We avoid storing the passphrase directly,
// instead we salt and rehash repeatedly
2018-01-04 23:05:01 -06:00
using ( var h = System . Security . Cryptography . SHA256 . Create ())
2013-08-17 22:05:40 +02:00
{
2013-08-21 22:24:36 +02:00
h . Initialize ();
2013-08-17 22:05:40 +02:00
h . TransformBlock ( salt , 0 , salt . Length , salt , 0 );
h . TransformFinalBlock ( data , 0 , data . Length );
var buf = h . Hash ;
2018-01-04 23:05:01 -06:00
for ( var i = 0 ; i < repeats ; i ++)
2013-08-17 22:05:40 +02:00
{
2013-08-21 22:24:36 +02:00
h . Initialize ();
2013-08-17 22:05:40 +02:00
h . TransformBlock ( salt , 0 , salt . Length , salt , 0 );
h . TransformFinalBlock ( buf , 0 , buf . Length );
buf = h . Hash ;
}
2018-01-04 23:05:01 -06:00
2013-08-17 22:05:40 +02:00
return buf ;
}
}
2017-09-14 23:16:07 -07:00
/// <summary>
/// Gets the drive letter from the given volume guid.
/// This method cannot be inlined since the System.Management types are not implemented in Mono
/// </summary>
/// <param name="volumeGuid">Volume guid</param>
/// <returns>Drive letter, as a single character, or null if the volume wasn't found</returns>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
public static string GetDriveLetterFromVolumeGuid ( Guid volumeGuid )
{
// Based on this answer:
// https://stackoverflow.com/questions/10186277/how-to-get-drive-information-by-volume-id
using ( System . Management . ManagementObjectSearcher searcher = new System . Management . ManagementObjectSearcher ( "Select * from Win32_Volume" ))
{
string targetId = string . Format ( @"\\?\Volume{{{0}}}\" , volumeGuid );
foreach ( System . Management . ManagementObject obj in searcher . Get ())
{
2017-09-18 23:23:45 -06:00
if ( string . Equals ( obj [ "DeviceID" ]. ToString (), targetId , StringComparison . OrdinalIgnoreCase ))
2017-09-14 23:16:07 -07:00
{
object driveLetter = obj [ "DriveLetter" ];
if ( driveLetter != null )
{
return obj [ "DriveLetter" ]. ToString ();
}
else
{
// The volume was found, but doesn't have a drive letter associated with it.
break ;
}
}
}
return null ;
}
}
/// <summary>
/// Gets all volume guids and their associated drive letters.
/// This method cannot be inlined since the System.Management types are not implemented in Mono
/// </summary>
/// <returns>Pairs of drive letter to volume guids</returns>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
public static IEnumerable < KeyValuePair < string , string >> GetVolumeGuidsAndDriveLetters ()
{
2018-04-20 17:30:08 +02:00
using ( var searcher = new System . Management . ManagementObjectSearcher ( "Select * from Win32_Volume" ))
2017-09-14 23:16:07 -07:00
{
2018-04-20 17:30:08 +02:00
foreach ( var obj in searcher . Get ())
2017-09-14 23:16:07 -07:00
{
2018-04-20 17:30:08 +02:00
var deviceIdObj = obj [ "DeviceID" ];
var driveLetterObj = obj [ "DriveLetter" ];
2017-09-14 23:16:07 -07:00
if ( deviceIdObj != null && driveLetterObj != null )
{
2018-04-20 17:30:08 +02:00
var deviceId = deviceIdObj . ToString ();
var driveLetter = driveLetterObj . ToString ();
2017-09-14 23:16:07 -07:00
if (! string . IsNullOrEmpty ( deviceId ) && ! string . IsNullOrEmpty ( driveLetter ))
{
yield return new KeyValuePair < string , string >( driveLetter + @"\" , deviceId );
}
}
}
}
}
2017-11-21 23:41:02 +01:00
/// <summary>
/// The regular expression matching all know non-quoted commandline characters
/// </summary>
private static readonly Regex COMMANDLINE_SAFE = new Regex ( @"[A-Za-z0-9\-_/:\.]*" );
/// <summary>
/// Special characters that needs to be escaped on Linux
/// </summary>
2019-09-19 22:09:50 +02:00
private static readonly Regex COMMANDLINE_ESCAPED_LINUX = new Regex ( @"[""$`\\!]" );
2017-11-21 23:41:02 +01:00
/// <summary>
/// Wraps a single argument in quotes suitable for the passing on the commandline
/// </summary>
/// <returns>The wrapped commandline element.</returns>
/// <param name="arg">The argument to wrap.</param>
/// <param name="allowEnvExpansion">A flag indicating if environment variables are allowed to be expanded</param>
public static string WrapCommandLineElement ( string arg , bool allowEnvExpansion )
{
if ( string . IsNullOrWhiteSpace ( arg ))
return arg ;
2018-11-02 22:13:25 +01:00
if (! Platform . IsClientWindows )
2017-11-21 23:41:02 +01:00
{
// We could consider using single quotes that prevents all expansions
//if (!allowEnvExpansion)
// return "'" + arg.Replace("'", "\\'") + "'";
2018-01-04 23:05:01 -06:00
2017-11-21 23:41:02 +01:00
// Linux is using backslash to escape, except for !
arg = COMMANDLINE_ESCAPED_LINUX . Replace ( arg , ( match ) =>
{
if ( match . Value == "!" )
2018-04-20 17:30:08 +02:00
return @"""'!'""" ;
2017-11-21 23:41:02 +01:00
if ( match . Value == "$" && allowEnvExpansion )
return match . Value ;
2018-01-04 23:05:01 -06:00
2018-04-20 17:30:08 +02:00
return @"\" + match . Value ;
2017-11-21 23:41:02 +01:00
});
}
else
{
// Windows needs only needs " replaced with "",
// but is prone to %var% expansion when used in
// immediate mode (i.e. from command prompt)
2017-11-21 23:42:48 +01:00
// Fortunately it does not expand when processes
// are started from within .Net
2017-11-21 23:41:02 +01:00
// TODO: I have not found a way to avoid escaping %varname%,
// and sadly it expands only if the variable exists
// making it even rarer and harder to diagnose when
// it happens
2018-04-20 17:30:08 +02:00
arg = arg . Replace ( @"""" , @"""""" );
2017-12-20 22:45:44 +01:00
// Also fix the case where the argument ends with a slash
if ( arg [ arg . Length - 1 ] == '\\' )
2018-04-20 17:30:08 +02:00
arg += @"\" ;
2017-11-21 23:41:02 +01:00
}
// Check that all characters are in the safe set
if ( COMMANDLINE_SAFE . Match ( arg ). Length != arg . Length )
2018-04-20 17:30:08 +02:00
return @"""" + arg + @"""" ;
2017-11-21 23:41:02 +01:00
else
2018-01-04 23:05:01 -06:00
return arg ;
2017-11-21 23:41:02 +01:00
}
/// <summary>
/// Wrap a set of commandline arguments suitable for the commandline
/// </summary>
/// <returns>A commandline string.</returns>
/// <param name="args">The arguments to create into a commandline.</param>
/// <param name="allowEnvExpansion">A flag indicating if environment variables are allowed to be expanded</param>
public static string WrapAsCommandLine ( IEnumerable < string > args , bool allowEnvExpansion = false )
{
return string . Join ( " " , args . Select ( x => WrapCommandLineElement ( x , allowEnvExpansion )));
}
2018-03-20 13:41:57 -06:00
/// <summary>
/// Utility method that emulates C#'s built in await keyword without requiring the calling method to be async.
/// This method should be preferred over using Task.Result, as it doesn't wrap singular exceptions in AggregateExceptions.
/// (It uses Task.GetAwaiter().GetResult(), which is the same thing that await uses under the covers.)
/// https://stackoverflow.com/questions/17284517/is-task-result-the-same-as-getawaiter-getresult
/// </summary>
/// <param name="task">Task to await</param>
public static void Await ( this Task task )
{
task . GetAwaiter (). GetResult ();
}
/// <summary>
/// Utility method that emulates C#'s built in await keyword without requiring the calling method to be async.
/// This method should be preferred over using Task.Result, as it doesn't wrap singular exceptions in AggregateExceptions.
/// (It uses Task.GetAwaiter().GetResult(), which is the same thing that await uses under the covers.)
/// https://stackoverflow.com/questions/17284517/is-task-result-the-same-as-getawaiter-getresult
/// </summary>
/// <typeparam name="T">Result type</typeparam>
/// <param name="task">Task to await</param>
/// <returns>Task result</returns>
public static T Await < T >( this Task < T > task )
{
return task . GetAwaiter (). GetResult ();
}
2022-01-15 18:11:59 +01:00
/// <summary>
/// Utility that computes the delay before the next retry of an operation, optionally using exponential backoff.
/// Note: when using exponential backoff, the exponent is clamped at 10.
/// </summary>
/// <param name="retryDelay">Value of one delay unit</param>
/// <param name="retryAttempt">The attempt number (e.g. 1 for the first retry, 2 for the second retry, etc.)</param>
/// <param name="useExponentialBackoff">Whether to use exponential backoff</param>
/// <returns>The computed delay</returns>
public static TimeSpan GetRetryDelay ( TimeSpan retryDelay , int retryAttempt , bool useExponentialBackoff )
{
if ( retryAttempt < 1 )
{
throw new ArgumentException ( "The attempt number must not be less than 1." , nameof ( retryAttempt ));
}
TimeSpan delay ;
if ( useExponentialBackoff )
{
var delayTicks = retryDelay . Ticks << Math . Min ( retryAttempt - 1 , 10 );
delay = TimeSpan . FromTicks ( delayTicks );
}
else
{
delay = retryDelay ;
}
return delay ;
}
2013-02-12 21:43:14 +00:00
}
2016-09-28 20:15:40 +02:00
}