PageRenderTime 82ms CodeModel.GetById 36ms app.highlight 32ms RepoModel.GetById 1ms app.codeStats 0ms

/Utilities/Helpers/FileHelper.cs

#
C# | 2070 lines | 1228 code | 189 blank | 653 comment | 104 complexity | b7a0f155bd58030704580436144e2d15 MD5 | raw file
Possible License(s): Apache-2.0
   1using System;
   2using System.Collections.Generic;
   3using System.IO;
   4using System.Security.Cryptography;
   5using System.Text;
   6using System.Threading;
   7using NUnit.Framework;
   8
   9namespace Delta.Utilities.Helpers
  10{
  11	/// <summary>
  12	/// File helper class to get text lines, number of text lines, etc. This
  13	/// extends the existing File and Path functionality of .NET.
  14	/// <para />
  15	/// This class also abstracts the use of File.Exists and File.Open for
  16	/// IsolatedStorage classes are used, which just allow access to the
  17	/// current directory, which is all we need in the engine internally).
  18	/// </summary>
  19	public static class FileHelper
  20	{
  21		#region Constants
  22		/// <summary>
  23		/// Represents the separator for folders in file paths on microsoft
  24		/// platforms.
  25		/// </summary>
  26		public static readonly char FolderSeparator = '\\';
  27
  28		/// <summary>
  29		/// Represents the separator for folders in file paths on Unix platforms.
  30		/// </summary>
  31		public static readonly char AlternateFolderSeparator = '/';
  32
  33		/// <summary>
  34		/// Both folder separators together in one char array, used for GetFilename
  35		/// </summary>
  36		public static readonly char[] AllFolderSeparators =
  37			new[]
  38			{
  39				FolderSeparator, AlternateFolderSeparator
  40			};
  41
  42		/// <summary>
  43		/// The typical file extension separator is just a dot: .
  44		/// </summary>
  45		public const char FileExtensionSeperator = '.';
  46		#endregion
  47
  48		#region Delegates
  49		internal delegate bool ExistsDelegate(string path);
  50
  51		internal delegate void CopyDelegate(string sourceFile,
  52			string destinationFile);
  53
  54		internal delegate void MoveDelegate(string sourceFile,
  55			string destinationFile);
  56
  57		internal delegate FileStream OpenDelegate(string filePath, FileMode mode,
  58			FileAccess access, FileShare share);
  59
  60		internal delegate void DeleteDelegate(string filePath);
  61
  62		internal delegate bool FileIsNewerDelegate(string fileToCheckIfNewer,
  63			string originalFile);
  64
  65		/// <summary>
  66		/// Compare two files with the help of the MD5-checksum
  67		/// </summary>
  68		/// <param name="fileName1">Filename 1</param>
  69		/// <param name="fileName2">Filename 2</param>
  70		/// <returns>True if both file contents are the same</returns>
  71		internal delegate bool CompareFilesDelegate(string fileName1,
  72			string fileName2);
  73
  74		/// <summary>
  75		/// Check if file exists and is not written to recently. This is mostly
  76		/// used for update checks to see if we can assume this file is complete
  77		/// and we can now open it, etc. (e.g. used in the RestartTool).
  78		/// </summary>
  79		/// <param name="filename">File to check</param>
  80		/// <returns>
  81		/// True if the file exists and was not written to recently.
  82		/// </returns>
  83		internal delegate bool FileNotWrittenToDelegate(string filename);
  84		#endregion
  85
  86		#region Exists (Static)
  87		/// <summary>
  88		/// Check if the file exists.
  89		/// </summary>
  90		/// <param name="filename">Filename</param>
  91		/// <returns>filename</returns>
  92		public static bool Exists(string filename)
  93		{
  94			return ExistsCallback(filename);
  95		}
  96		#endregion
  97
  98		#region Copy (Static)
  99		/// <summary>
 100		/// Copy the source file to the destination file.
 101		/// Note: this method overwrites existing destination files!
 102		/// </summary>
 103		/// <param name="sourceFile">Source filepath.</param>
 104		/// <param name="destinationFile">Destination Filepath.</param>
 105		public static void Copy(string sourceFile, string destinationFile)
 106		{
 107			CopyCallback(sourceFile, destinationFile);
 108		}
 109		#endregion
 110
 111		#region Move (Static)
 112		/// <summary>
 113		/// Move the source file to the destination file.
 114		/// </summary>
 115		/// <param name="sourceFile">Source filepath.</param>
 116		/// <param name="destinationFile">Destination filepath.</param>
 117		public static void Move(string sourceFile, string destinationFile)
 118		{
 119			MoveCallback(sourceFile, destinationFile);
 120		}
 121		#endregion
 122
 123		#region Open (Static)
 124		/// <summary>
 125		/// Opens a the given file (will not create it if it doesn't exists) on
 126		/// platforms where this is possible.
 127		/// <para />
 128		/// Note: The file-mode is "Open" only and the file-access + file-share
 129		/// mode is "Read".
 130		/// </summary>
 131		public static FileStream Open(string filePath)
 132		{
 133			return Open(filePath, FileMode.Open, FileAccess.Read,
 134				FileShare.Read);
 135		}
 136
 137		/// <summary>
 138		/// Open, will use File.Open on platforms where this is possible.
 139		/// </summary>
 140		public static FileStream Open(string filePath, FileMode mode,
 141			FileAccess access, FileShare share)
 142		{
 143			return OpenCallback(filePath, mode, access, share);
 144		}
 145		#endregion
 146
 147		#region Rename (Static)
 148		/// <summary>
 149		/// Rename the file.
 150		/// </summary>
 151		/// <param name="sourceFile">Path to the file to rename.</param>
 152		/// <param name="destFile">Path to the file after rename.</param>
 153		/// <returns>True if succeeded otherwise false.</returns>
 154		public static bool Rename(string sourceFile, string destFile)
 155		{
 156			if (Exists(sourceFile) == false)
 157			{
 158				return false;
 159			}
 160
 161			try
 162			{
 163				Move(sourceFile, destFile);
 164			}
 165			catch
 166			{
 167				return false;
 168			}
 169
 170			return true;
 171		}
 172		#endregion
 173
 174		#region GetFilename (Static)
 175		/// <summary>
 176		/// Extracts filename from full path+filename, but don't cuts off the
 177		/// extension. Can be also used to cut of directories from a path (only
 178		/// last one will remain).
 179		/// </summary>
 180		/// <returns>filename</returns>
 181		public static string GetFilename(string filePath)
 182		{
 183			// Similar code as GetFilename(..), but this method is called more often!
 184
 185			#region Validation
 186			if (String.IsNullOrEmpty(filePath))
 187			{
 188				return "";
 189			}
 190			#endregion
 191
 192			// At first we try to find any folder separators (incl. alternate ones)
 193			int lastSeparatorIndex = filePath.LastIndexOf(FolderSeparator);
 194			int lastAlternateSeperatorIndex = filePath.LastIndexOf(
 195				AlternateFolderSeparator);
 196			// to pick the backmost one
 197			int backMostIndex = (lastSeparatorIndex > lastAlternateSeperatorIndex)
 198			                    	? lastSeparatorIndex
 199			                    	: lastAlternateSeperatorIndex;
 200
 201			// to get only the filename
 202			return (backMostIndex != MathHelper.InvalidIndex)
 203			       	? // -> "Path\SubPath\File.tst" -> "File.tst"
 204			       // -> "Path/SubPath\File.tst" -> "File.tst"
 205			       // -> "Path\SubPath/File.tst" -> "File.tst"
 206			       filePath.Substring(backMostIndex + 1)
 207			       	: // -> "File.tst" -> "File.tst"
 208			       filePath;
 209		}
 210
 211		/// <summary>
 212		/// Extracts filename from full path+filename, but don't cuts off the
 213		/// extension. Can be also used to cut of directories from a path (only
 214		/// last one will remain).
 215		/// </summary>
 216		public static string GetFilename(string filePath, out string pathOnly)
 217		{
 218			#region Validation
 219			if (String.IsNullOrEmpty(filePath))
 220			{
 221				pathOnly = "";
 222				return "";
 223			}
 224			#endregion
 225
 226			// At first we try to find any folder separators (incl. alternate ones)
 227			int lastSeparatorIndex = filePath.LastIndexOf(FolderSeparator);
 228			int lastAlternateSeperatorIndex = filePath.LastIndexOf(
 229				AlternateFolderSeparator);
 230			// to pick the backmost one
 231			int backMostIndex = lastSeparatorIndex > lastAlternateSeperatorIndex
 232			                    	? lastSeparatorIndex
 233			                    	: lastAlternateSeperatorIndex;
 234
 235			if (backMostIndex != MathHelper.InvalidIndex)
 236			{
 237				// -> "Path\SubPath\File.tst" -> "File.tst"
 238				// -> "Path/SubPath\File.tst" -> "File.tst"
 239				// -> "Path\SubPath/File.tst" -> "File.tst"
 240				pathOnly = filePath.Substring(0, backMostIndex);
 241				return filePath.Substring(backMostIndex + 1);
 242			}
 243			else
 244			{
 245				// -> "File.tst" -> "File.tst"
 246				pathOnly = "";
 247				return filePath;
 248			}
 249		}
 250
 251		/// <summary>
 252		/// Extracts filename from full path+filename, cuts of extension
 253		/// if cutExtension is true. Can be also used to cut of directories
 254		/// from a path (only last one will remain).
 255		/// </summary>
 256		public static string GetFilename(string filePath, bool cutExtension)
 257		{
 258			#region Validation
 259			if (String.IsNullOrEmpty(filePath))
 260			{
 261				return "";
 262			}
 263			#endregion
 264
 265			// At first we try to find any folder separators (incl. alternate ones)
 266			int lastSeparatorIndex = filePath.LastIndexOf(FolderSeparator);
 267			int lastAlternateSeperatorIndex = filePath.LastIndexOf(
 268				AlternateFolderSeparator);
 269			// to pick the backmost one
 270			int backMostIndex =
 271				lastSeparatorIndex > lastAlternateSeperatorIndex
 272					? lastSeparatorIndex
 273					: lastAlternateSeperatorIndex;
 274
 275			// Convert to filename, examples:
 276			// -> "Path\SubPath\File.tst" -> "File.tst"
 277			// -> "Path/SubPath\File.tst" -> "File.tst"
 278			// -> "Path\SubPath/File.tst" -> "File.tst"
 279			// -> "File.tst" -> "File.tst"
 280			string filename =
 281				backMostIndex != MathHelper.InvalidIndex
 282					? filePath.Substring(backMostIndex + 1)
 283					: filePath;
 284
 285			// Return the filename without path (and optionally cut of the extension)
 286			// -> "File.tst" -> "File"
 287			return
 288				cutExtension
 289					? CutExtension(filename)
 290					: filename;
 291		}
 292		#endregion
 293
 294		#region IsFilenameOnly (Static)
 295		/// <summary>
 296		/// Is filename only
 297		/// </summary>
 298		/// <param name="anyFilePath">Any file path</param>
 299		public static bool IsFilenameOnly(string anyFilePath)
 300		{
 301			if (String.IsNullOrEmpty(anyFilePath) ||
 302			    // If this is a filename, it better has an extension. Note: This is
 303			    // also used for GetContentName, so all content follows this rule too.
 304			    HasFileNoExtension(anyFilePath))
 305			{
 306				return false;
 307			}
 308
 309			// Make sure the filename does not contain any path elements
 310			if (anyFilePath.IndexOf(FolderSeparator) >= 0 ||
 311			    anyFilePath.IndexOf(AlternateFolderSeparator) >= 0)
 312			{
 313				return false;
 314			}
 315
 316			return true;
 317		}
 318		#endregion
 319
 320		#region HasFileNoExtension (Static)
 321		/// <summary>
 322		/// Has a filename no extension
 323		/// </summary>
 324		/// <param name="anyFilePath">Any file path</param>
 325		/// <returns>True if the given file path has no extension.</returns>
 326		public static bool HasFileNoExtension(string anyFilePath)
 327		{
 328			if (String.IsNullOrEmpty(anyFilePath))
 329			{
 330				return false;
 331			}
 332
 333			// The file extension is always separated with the '.' defined above.
 334			// If we cannot find it, this file has no extension.
 335			return anyFilePath.LastIndexOf(FileExtensionSeperator) < 0;
 336		}
 337		#endregion
 338
 339		#region GetDirectoryPath (Static)
 340		/// <summary>
 341		/// Get directory of path+File, if only a path is given we will cut off
 342		/// the last sub path!
 343		/// </summary>
 344		/// <param name="filePath">File path</param>
 345		/// <returns>Directory path</returns>
 346		public static string GetDirectoryPath(string filePath)
 347		{
 348			// pathFile must be valid
 349			if (String.IsNullOrEmpty(filePath))
 350			{
 351				return "";
 352			}
 353
 354			// If pathFile ends with a folder separator, cut that off!
 355			if (filePath.EndsWith(FolderSeparator.ToString()) ||
 356			    filePath.EndsWith(AlternateFolderSeparator.ToString()))
 357			{
 358				filePath = filePath.Substring(0, filePath.Length - 1);
 359			}
 360
 361			// Normal directory check
 362			int index = filePath.LastIndexOf(FolderSeparator);
 363			int alternateIndex = filePath.LastIndexOf(AlternateFolderSeparator);
 364
 365			if (alternateIndex > index)
 366			{
 367				index = alternateIndex;
 368			}
 369
 370			if (index >= 0 &&
 371			    index < filePath.Length)
 372			{
 373				// Return directory
 374				return filePath.Substring(0, index);
 375			}
 376
 377			// No sub directory found (parent of some directory is "")
 378			// Only exception is when we are on the file system root level (e.g. c:)
 379			return
 380				filePath.Contains(":")
 381					? filePath
 382					: "";
 383		}
 384
 385		/// <summary>
 386		/// Get directory sub path of the given path or file path.
 387		/// (-> e.g. "FileHelper.GetDirectoryPath(@"C:\Ab\cde\App.exe", 2)" will
 388		/// return "C:\Ab")
 389		/// </summary>
 390		/// <param name="filePath">File path</param>
 391		/// <param name="jumpbackCount">
 392		/// The number of folders it should be "jumped" back.
 393		/// </param>
 394		/// <returns>Directory path</returns>
 395		public static string GetDirectoryPath(string filePath, int jumpbackCount)
 396		{
 397			// pathFile must be valid
 398			if (String.IsNullOrEmpty(filePath))
 399			{
 400				return "";
 401			}
 402
 403			// At first split the path in its folder (+ filename) parts
 404			string[] pathParts = filePath.Split(AllFolderSeparators);
 405
 406			// If we less or equal (folder) parts than folders we want to jump back
 407			int remainingPartCount = pathParts.Length - jumpbackCount;
 408			if (remainingPartCount <= 0)
 409			{
 410				// then we will just return an empty path
 411				return "";
 412			}
 413
 414			// Now just count the number of characters we have to cut off
 415			int cutOffCount = 0;
 416			for (int index = remainingPartCount; index < pathParts.Length; index++)
 417			{
 418				cutOffCount += pathParts[index].Length + 1;
 419			}
 420
 421			// And finally just return the remaining path
 422			return filePath.Substring(0, filePath.Length - cutOffCount);
 423		}
 424		#endregion
 425
 426		#region GetFirstDirectoryName (Static)
 427		/// <summary>
 428		/// Get first directory of path or file path! Returns "c:" for "C:\test.x"
 429		/// or "bla" for "bla\blub\honk.txt".
 430		/// </summary>
 431		/// <param name="pathFile">Path file to search</param>
 432		/// <returns>First found directory part</returns>
 433		public static string GetFirstDirectoryName(string pathFile)
 434		{
 435			if (String.IsNullOrEmpty(pathFile))
 436			{
 437				return "";
 438			}
 439
 440			string[] parts = pathFile.Split(new char[]
 441			{
 442				'/', '\\'
 443			});
 444			// Just return the first part
 445			return parts[0];
 446		}
 447		#endregion
 448
 449		#region GetLastDirectoryName (Static)
 450		/// <summary>
 451		/// Get last directory of path+File, if only a path is given we will cut
 452		/// off the last sub path! Returns "Models" for "C:\aoeus\Models\test.x".
 453		/// Warning: If you just use a path, the results might be different from
 454		/// what you want (use GetFilename instead): ""C:\aoeus\Models" will return
 455		/// "aoeus" and not "Models".
 456		/// </summary>
 457		/// <param name="pathFile">Path file to search</param>
 458		/// <returns>Last found directory</returns>
 459		public static string GetLastDirectoryName(string pathFile)
 460		{
 461			if (String.IsNullOrEmpty(pathFile))
 462			{
 463				return "";
 464			}
 465
 466			string dir1 = GetDirectoryPath(pathFile);
 467			string dir2 = GetDirectoryPath(dir1);
 468
 469			// No other sub directory found?
 470			if (String.IsNullOrEmpty(dir2))
 471			{
 472				return dir1;
 473			}
 474
 475			// Remove dir2 part including "\\" or "/"
 476			return dir1.Remove(0, dir2.Length + 1);
 477		}
 478		#endregion
 479
 480		#region RemoveFirstDirectory (Static)
 481		/// <summary>
 482		/// Remove first directory of path if one exists. "maps\\mymaps\\hehe.map"
 483		/// becomes "mymaps\\hehe.map". Also used to cut first folder off,
 484		/// especially useful for paths. e.g. "maps\\test" becomes "test".
 485		/// </summary>
 486		/// <param name="path">Path</param>
 487		/// <returns>Reduced directory path</returns>
 488		public static string RemoveFirstDirectory(string path)
 489		{
 490			int index = path.IndexOf(FolderSeparator);
 491			if (index >= 0 &&
 492			    index < path.Length)
 493			{
 494				// Return rest of path
 495				return path.Substring(index + 1);
 496			}
 497			index = path.IndexOf(AlternateFolderSeparator);
 498			if (index >= 0 &&
 499			    index < path.Length)
 500			{
 501				// Return rest of path
 502				return path.Substring(index + 1);
 503			}
 504
 505			// No first directory found, just return original path
 506			return path;
 507		}
 508		#endregion
 509
 510		#region CutExtension (Static)
 511		/// <summary>
 512		/// Cut of extension, e.g. "hi.txt" becomes "hi"
 513		/// </summary>
 514		/// <param name="file">File</param>
 515		/// <returns>Filename without extension</returns>
 516		public static string CutExtension(string file)
 517		{
 518			if (String.IsNullOrEmpty(file))
 519			{
 520				return "";
 521			}
 522
 523			// Don't cut below any directory structure (which may contain points)
 524			int maxLastSeparatorIndex = file.LastIndexOf(FolderSeparator);
 525			int alternateSeparatorIndex = file.LastIndexOf(AlternateFolderSeparator);
 526			if (alternateSeparatorIndex > maxLastSeparatorIndex)
 527			{
 528				maxLastSeparatorIndex = alternateSeparatorIndex;
 529			}
 530
 531			// Get extension position.
 532			int lastPointPos = file.LastIndexOf(FileExtensionSeperator);
 533
 534			// Only cut off stuff if we have valid indices, else return the input
 535			return
 536				lastPointPos > 0 &&
 537				lastPointPos > maxLastSeparatorIndex
 538					? file.Remove(lastPointPos, file.Length - lastPointPos)
 539					: file;
 540		}
 541		#endregion
 542
 543		#region GetExtension (Static)
 544		/// <summary>
 545		/// Get extension (whatever is behind that '.'), e.g. "test.bmp" will
 546		/// return "bmp". Only a filename will return "", e.g. "Test" returns ""
 547		/// </summary>
 548		/// <param name="file">Filename to get the extension from</param>
 549		/// <returns>Returns the extension string of the file.</returns>
 550		public static string GetExtension(string file)
 551		{
 552			if (String.IsNullOrEmpty(file))
 553			{
 554				return "";
 555			}
 556
 557			int lastPointPos = file.LastIndexOf(FileExtensionSeperator);
 558
 559			// Don't cut below any directory structure (which may contain points)
 560			int lastDirectoryPos = file.LastIndexOf(FolderSeparator);
 561			int alternateSeparatorIndex = file.LastIndexOf(AlternateFolderSeparator);
 562			if (alternateSeparatorIndex > lastDirectoryPos)
 563			{
 564				lastDirectoryPos = alternateSeparatorIndex;
 565			}
 566			if (lastPointPos > 0 &&
 567			    lastPointPos > lastDirectoryPos &&
 568			    lastPointPos < file.Length)
 569			{
 570				return file.Remove(0, lastPointPos + 1);
 571			}
 572
 573			// No extension found, return empty string (filename without extension).
 574			return "";
 575		}
 576		#endregion
 577
 578		#region TryToUseRelativePath (Static)
 579		/// <summary>
 580		/// Helper function for saving, we check if path starts with same as
 581		/// our application. If so, better use relative path, then we can use
 582		/// them even if application is moved or copied over network!
 583		/// </summary>
 584		/// <param name="fullPath">Full path to check against</param>
 585		/// <returns>Relative path starting at fullPath if possible</returns>
 586		public static string TryToUseRelativePath(string fullPath)
 587		{
 588			return TryToUseRelativePath(Environment.CurrentDirectory, fullPath);
 589		}
 590
 591		/// <summary>
 592		/// Helper function for saving, we check if path starts with same as
 593		/// our application. If so, better use relative path, then we can use
 594		/// them even if application is moved or copied over network!
 595		/// </summary>
 596		/// <param name="pathToCheckFrom">Path to check from (e.g. from our
 597		/// application) and the origin for the relative path that is returned
 598		/// </param>
 599		/// <param name="fullPath">Full path to check against</param>
 600		/// <returns>Relative path starting at fullPath if possible</returns>
 601		public static string TryToUseRelativePath(string pathToCheckFrom,
 602			string fullPath)
 603		{
 604			if (fullPath != null &&
 605			    fullPath.StartsWith(pathToCheckFrom))
 606			{
 607				int len = pathToCheckFrom.Length;
 608				// +1 to remove '/' too
 609				if (!pathToCheckFrom.EndsWith(@"\"))
 610				{
 611					len++;
 612				}
 613				return fullPath.Remove(0, len);
 614			} // if (fullPath)
 615			// Startup path not found, so either its relative already or
 616			// we can't use relative path that easy!
 617			return fullPath;
 618		}
 619		#endregion
 620
 621		#region TryToUseAbsolutePath (Static)
 622		/// <summary>
 623		/// Helper method to use an absolute path. If the path is already absolute
 624		/// nothing will change, but if the path is relative the basePath is added.
 625		/// </summary>
 626		/// <param name="basePath">Path to check from</param>
 627		/// <param name="possibleFullPath">Full or relative path to check.</param>
 628		/// <returns>Absolute path to the file</returns>
 629		public static string TryToUseAbsolutePath(string basePath,
 630			string possibleFullPath)
 631		{
 632			// If the path is relative, combine it
 633			if (Path.IsPathRooted(possibleFullPath) == false)
 634			{
 635				return Path.Combine(basePath, possibleFullPath);
 636			}
 637			// Otherwise just return the original absolute path.
 638			return possibleFullPath;
 639		}
 640		#endregion
 641
 642		#region IsDirectSubFolder (Static)
 643		/// <summary>
 644		/// Check if a folder is a direct sub folder of a main folder.
 645		/// True is only returned if this is a direct sub folder, not if
 646		/// it is some sub folder few levels below.
 647		/// </summary>
 648		/// <param name="mainFolder">MainFolder</param>
 649		/// <param name="subFolder">SubFolder</param>
 650		/// <returns>
 651		/// True if the subFolder is a direct sub folder of mainFolder.
 652		/// </returns>
 653		public static bool IsDirectSubFolder(string subFolder, string mainFolder)
 654		{
 655			// First check if subFolder is really a sub folder of mainFolder
 656			if (subFolder != null &&
 657			    subFolder.StartsWith(mainFolder))
 658			{
 659				// Same order?
 660				if (subFolder.Length <
 661				    mainFolder.Length + 1)
 662				{
 663					// Then it ain't a sub folder!
 664					return false;
 665				}
 666
 667				// Ok, now check if this is direct sub folder or some sub folder
 668				// of mainFolder sub folder
 669				string folder = subFolder.Remove(0, mainFolder.Length + 1);
 670
 671				// Check if this is really a direct sub folder
 672				if (folder.IndexOf(FolderSeparator) >= 0 ||
 673				    folder.IndexOf(AlternateFolderSeparator) >= 0)
 674				{
 675					// No, this is a sub folder of mainFolder sub folder
 676					return false;
 677				}
 678
 679				// Ok, this is a direct sub folder of mainFolder!
 680				return true;
 681			}
 682
 683			// Not even any sub folder!
 684			return false;
 685		}
 686		#endregion
 687
 688		#region CleanupWindowsPath (Static)
 689		/// <summary>
 690		/// Cleans up the given path, so that it's in an homogenous unix/web path.
 691		/// E.g. ".\MyPath\With/Subfolder" => "My/Path/With/Subfolder"
 692		/// Also used by our whole content pipeline to make searching easier.
 693		/// </summary>
 694		/// <param name="anyPath">Any path in windows or unix format</param>
 695		/// <returns>Path in windows format</returns>
 696		public static string CleanupWindowsPath(string anyPath)
 697		{
 698			// Validation check
 699			if (String.IsNullOrEmpty(anyPath))
 700			{
 701				return "";
 702			}
 703
 704			// First we make sure the we have an homogenous windows path format
 705			if (anyPath.StartsWith("file:///"))
 706			{
 707				anyPath = anyPath.Substring("file:///".Length);
 708			}
 709			anyPath = anyPath.Replace("/", @"\");
 710
 711			// If the path begins with "./" like e.g. ".\MyFolder\With\Subfolder",
 712			// then we cut this away, because we don't need that and it makes the
 713			// path more tight, might happen on windows
 714			// else return the cleaned path
 715			return
 716				anyPath.StartsWith(@".\")
 717					? anyPath.Substring(2)
 718					: anyPath;
 719		}
 720		#endregion
 721
 722		#region CleanupLinuxPath (Static)
 723		/// <summary>
 724		/// Cleans up the given path, so that it's in an homogenous unix/web path.
 725		/// E.g. ".\MyPath\With/Subfolder" => "My/Path/With/Subfolder"
 726		/// Also used by our whole content pipeline to make searching easier.
 727		/// </summary>
 728		/// <param name="anyPath">Any path in windows or unix format</param>
 729		/// <returns>Path in unix format</returns>
 730		public static string CleanupLinuxPath(string anyPath)
 731		{
 732			// Validation check
 733			if (String.IsNullOrEmpty(anyPath))
 734			{
 735				return "";
 736			}
 737
 738			// First we make sure the we have an homogenous unix/web path format
 739			if (anyPath.StartsWith(@"\\"))
 740			{
 741				anyPath = "file://///" + anyPath.Substring(2);
 742			}
 743			anyPath = anyPath.Replace(@"\", "/");
 744
 745			// If the path begins with "./" like e.g. "./MyFolder/With/Subfolder",
 746			// then we cut this away, because we don't need that and it makes the
 747			// else return the cleaned path
 748			return
 749				anyPath.StartsWith("./")
 750					? anyPath.Substring(2)
 751					: anyPath;
 752		}
 753		#endregion
 754
 755		#region GetRelativeFilePath (Static)
 756		/// <summary>
 757		/// Get relative file path of a given absolute file path based on basePath.
 758		/// If the basePath and the absoluteFilePath share something in common this
 759		/// method will try to remove the common part (e.g. "c:\code\Delta\bla"
 760		/// in the basePath "c:\code" will be reduced to "Delta\bla" or in the
 761		/// basePath "c:\code\DeltaEngine" will be "..\Delta\bla").
 762		/// </summary>
 763		/// <param name="absoluteFilePath">
 764		/// Absolute file path, might already be relative or even be on a different
 765		/// drive, then it will just be returned.
 766		/// </param>
 767		/// <param name="basePath">Base path</param>
 768		/// <returns>Relative path if possible</returns>
 769		public static string GetRelativeFilePath(string absoluteFilePath,
 770			string basePath)
 771		{
 772			#region Validation checks
 773			if (String.IsNullOrEmpty(absoluteFilePath))
 774			{
 775				return "";
 776			}
 777
 778			if (String.IsNullOrEmpty(basePath))
 779			{
 780				return absoluteFilePath;
 781			}
 782			#endregion
 783
 784			// We can only easily "build" the relative content path, if the given
 785			// file path starts with the given content base path.
 786			string relativeContentFilePath =
 787				absoluteFilePath.StartsWith(basePath)
 788					? absoluteFilePath.Substring(basePath.Length)
 789					: absoluteFilePath;
 790
 791			// If that did not worked, we can also check folder by folder if
 792			// the two path strings are equal up to a certain point.
 793			if (relativeContentFilePath == absoluteFilePath)
 794			{
 795				string[] basePathParts = basePath.Split(new char[]
 796				{
 797					'\\', '/'
 798				});
 799				string[] absoluteFilePathParts = absoluteFilePath.Split(new char[]
 800				{
 801					'\\', '/'
 802				});
 803
 804				// Continue checking as long as the parts are equal. Note: This will
 805				// abort in the first loop when the drives are different or one part
 806				// is not absolute.
 807				int num = 0;
 808				for (; num < basePathParts.Length &&
 809					num < absoluteFilePathParts.Length; num++)
 810				{
 811					if (basePathParts[num] != absoluteFilePathParts[num])
 812					{
 813						break;
 814					}
 815					// Remove this part as it is equal (plus the separator)!
 816					relativeContentFilePath =
 817						relativeContentFilePath.Substring(basePathParts[num].Length + 1);
 818				}
 819
 820				// Nothing matched? Then just return the absolute path again!
 821				if (num == 0)
 822				{
 823					return absoluteFilePath;
 824				}
 825
 826				// Otherwise add the number of parts we are away from the base path.
 827				for (; num < basePathParts.Length; num++)
 828				{
 829					relativeContentFilePath = "..\\" + relativeContentFilePath;
 830				}
 831			}
 832
 833			// If the path starts with a "folder backslash" like "\Folder" then cut
 834			// it off
 835			return
 836				relativeContentFilePath.StartsWith(@"\")
 837					? relativeContentFilePath.Substring(1)
 838					: relativeContentFilePath;
 839		}
 840		#endregion
 841		
 842		#region TrimPath (Static)
 843		/// <summary>
 844		/// Removes all unnecessary relative path fragments and will return a
 845		/// trimmed path. Useful in combination with GetRelativeFilePath.
 846		/// <para />
 847		/// Example: "C:\Path\SubPath1\..\SubPath2\file.exe" will become to
 848		/// "C:\Path\SubPath2\file.exe". Also works with relative paths and unix
 849		/// paths (e.g. '/SomePath/../SomeFile.xml' will become 'SomeFile.xml').
 850		/// </summary>
 851		/// <param name="filePath">Path to be shortened if possible.</param>
 852		public static string TrimPath(string filePath)
 853		{
 854			// If the filePath has no ..\ in it, ignore it and just return it!
 855			if (String.IsNullOrEmpty(filePath) ||
 856				(filePath.Contains("..\\") == false &&
 857				filePath.Contains("../") == false))
 858			{
 859				return filePath;
 860			}
 861
 862			// Network paths need the \\ added again after we are done processing
 863			bool isNetworkPath = filePath.StartsWith(@"\\");
 864
 865			// Go through the path and collect all parts.
 866			string[] pathParts = filePath.Split(new char[]
 867			{
 868				'\\', '/'
 869			});
 870
 871			// Now collect all .. parts and cut off previous directories!
 872			List<string> result = new List<string>();
 873			foreach (string part in pathParts)
 874			{
 875				// Also ignore the . and empty parts (e.g. \ or at the beginning).
 876				if (String.IsNullOrEmpty(part))
 877				{
 878					continue;
 879				}
 880				// For .. entries remove the last real path entry we added
 881				if (result.Count > 0 &&
 882				    result[result.Count - 1] != ".." &&
 883				    part == "..")
 884				{
 885					result.RemoveAt(result.Count - 1);
 886				}
 887				else
 888				{
 889					// Otherwise add the path part normally (might even be .. if there
 890					// is nothing more we can cut off).
 891					result.Add(part);
 892				}
 893			}
 894
 895			// Return the result in windows path mode if the incoming path contained
 896			// backslashes, otherwise return it in linux format.
 897			return
 898				(isNetworkPath
 899				 	? @"\\"
 900				 	: "") +
 901				ArrayHelper.Write(result,
 902					filePath.Contains("\\")
 903						? "\\"
 904						: "/");
 905		}
 906		#endregion
 907
 908		#region GetFileSize (Static)
 909		/// <summary>
 910		/// Get file size. Returns 0 if file does not exists instead of throwing
 911		/// an exception. Else it returns file size as a long number.
 912		/// </summary>
 913		/// <param name="filename">Filename</param>
 914		/// <returns>Long</returns>
 915		public static long GetFileSize(string filename)
 916		{
 917			if (Exists(filename))
 918			{
 919				using (FileStream fileStream = Open(filename,
 920					FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
 921				{
 922					return fileStream.Length;
 923				}
 924			}
 925
 926			return 0;
 927		}
 928		#endregion
 929
 930		#region GetLines (Static)
 931		/// <summary>
 932		/// Returns the text lines we got in a file.
 933		/// </summary>
 934		/// <param name="filename">Filename</param>
 935		/// <returns>
 936		/// All text lines from the file or null if no file was found.
 937		/// </returns>
 938		public static string[] GetLines(string filename)
 939		{
 940			return GetLines(filename, Encoding.UTF8);
 941		}
 942
 943		/// <summary>
 944		/// Returns the text lines we got in a file. Will not crash! If file does
 945		/// not exist, we will just return null, same for an unreadable file.
 946		/// </summary>
 947		/// <param name="filename">Filename</param>
 948		/// <param name="textEncoding">Text encoding</param>
 949		/// <returns>
 950		/// All text lines from the file or null if no file was found.
 951		/// </returns>
 952		public static string[] GetLines(string filename, Encoding textEncoding)
 953		{
 954			// If filename is invalid or file does not exist, just abort!
 955			if (String.IsNullOrEmpty(filename) ||
 956			    Exists(filename) == false)
 957			{
 958				return null;
 959			}
 960
 961			try
 962			{
 963				List<string> lines = new List<string>();
 964
 965				using (FileStream stream = Open(filename, FileMode.Open,
 966					FileAccess.Read, FileShare.ReadWrite))
 967				{
 968					StreamReader reader = new StreamReader(stream, textEncoding);
 969					do
 970					{
 971						lines.Add(reader.ReadLine());
 972					} while (reader.Peek() >
 973					         -1);
 974				}
 975
 976				return lines.ToArray();
 977			}
 978			catch (Exception ex)
 979			{
 980				Log.Warning(String.Format("Failed to GetLines of file={0}: {1}",
 981					filename, ex));
 982				// Failed to read, just return null!
 983				return null;
 984			}
 985		}
 986		#endregion
 987
 988		#region GetText (Static)
 989		/// <summary>
 990		/// Gets all the text of a file (e.g. if you just want to the text of a
 991		/// text file). Will not crash! If file does not exist, we will just return
 992		/// an empty string. If file is somehow unreadable, we will log an warning
 993		/// (because this shouldn't happen, we should always open files in a way
 994		/// that allows reading) and return an empty string too.
 995		/// </summary>
 996		/// <param name="filename">Filename for the file to load</param>
 997		/// <returns>String with all the text from the file</returns>
 998		public static string GetText(string filename)
 999		{
1000			string error;
1001			string text = GetText(filename, out error);
1002
1003			// Log out the error if there was one
1004			if (String.IsNullOrEmpty(error) == false)
1005			{
1006				Log.Warning(error);
1007			}
1008
1009			return text;
1010		}
1011
1012		/// <summary>
1013		/// Gets all the text of a file (e.g. if you just want to the text of a
1014		/// text file). Will not crash! If file does not exist, we will just return
1015		/// an empty string. If file is somehow unreadable, we will log an warning
1016		/// (because this shouldn't happen, we should always open files in a way
1017		/// that allows reading) and return an empty string too.
1018		/// </summary>
1019		/// <param name="filename">Filename for the file to load</param>
1020		/// <returns>String with all the text from the file</returns>
1021		public static string GetText(string filename, out string error)
1022		{
1023			// If filename is invalid or file does not exist, just abort!
1024			if (String.IsNullOrEmpty(filename) ||
1025			    Exists(filename) == false)
1026			{
1027				error = "GetText failed to get non existing file: '" + filename + "'";
1028				return "";
1029			}
1030
1031			try
1032			{
1033				using (StreamReader reader =
1034					new StreamReader(OpenFileAsStream(filename), Encoding.UTF8))
1035				{
1036					error = "";
1037					return reader.ReadToEnd();
1038				}
1039			}
1040			catch (Exception ex)
1041			{
1042				error = "Failed to GetText of file='" + filename + "': " + ex.Message;
1043				// Failed to read, just return an empty string!
1044				return "";
1045			}
1046		}
1047		#endregion
1048
1049		#region PathCombine (Static)
1050		/// <summary>
1051		/// Path combining methods to ensure each path is compatible. This is
1052		/// more powerful than Path.Combine and handles all platform cases and
1053		/// should be used if you need your code to run on all platforms.
1054		/// </summary>
1055		/// <param name="path1">Path 1</param>
1056		/// <param name="path2">Path 2</param>
1057		/// <returns>Combined path</returns>
1058		public static string PathCombine(string path1, string path2)
1059		{
1060			// If either one of the two paths are empty/null, return nothing.
1061			if ((path1 == null) ||
1062			    (path2 == null))
1063			{
1064				return "";
1065			}
1066
1067			// If either path contains the wrong "slash", revert to the correct one
1068			if ((path1.Contains(@"\")) ||
1069			    (path2.Contains(@"\")))
1070			{
1071				path1 = path1.Replace(@"\", "/");
1072				path2 = path2.Replace(@"\", "/");
1073			}
1074
1075			// If either path starts with either a "/" or a ".", remove them
1076			if (path1.StartsWith(@"/") ||
1077			    path1.StartsWith("."))
1078			{
1079				path1 = path1.Remove(0, 1);
1080			}
1081			if (path2.StartsWith(@"/") ||
1082			    path2.StartsWith("."))
1083			{
1084				path2 = path2.Remove(0, 1);
1085			}
1086
1087			// If either path ends with either a "/" or a ".", remove them.
1088			if (path1.EndsWith(@"/") ||
1089			    path1.EndsWith("."))
1090			{
1091				path1 = path1.Substring(0, path1.Length - 1);
1092			}
1093			if (path2.EndsWith(@"/") ||
1094			    path2.EndsWith("."))
1095			{
1096				path2 = path2.Substring(0, path2.Length - 1);
1097			}
1098
1099			// Return our combined path with the correct seperator
1100			return (path1 + "/" + path2);
1101		}
1102		#endregion
1103
1104		#region GetLinesCount (Static)
1105		/// <summary>
1106		/// Returns the number of text lines we got in a text file.
1107		/// </summary>
1108		/// <param name="filename">Filename</param>
1109		/// <returns>Number of lines in the given text file.</returns>
1110		public static int GetLinesCount(string filename)
1111		{
1112			int lines = 0;
1113
1114			try
1115			{
1116				using (StreamReader reader = new StreamReader(Open(
1117					filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite),
1118					Encoding.UTF8))
1119				{
1120					do
1121					{
1122						reader.ReadLine();
1123						lines++;
1124					} while (reader.Peek() >
1125					         -1);
1126				}
1127			}
1128			catch
1129			{
1130			} // catch
1131
1132			// Failed to read, just return 0 lines!
1133			return lines;
1134		}
1135		#endregion
1136
1137		#region CreateTextFile (Static)
1138		/// <summary>
1139		/// Create text file or overwrite an existing file. Will store all the
1140		/// given text into it with the optional Encoding (default is UTF8).
1141		/// </summary>
1142		/// <param name="filename">Filename to save text to</param>
1143		/// <param name="textForFile">Text to store in the new file</param>
1144		/// <param name="encoding">
1145		/// Encoding to use for file, by default UTF8
1146		/// </param>
1147		/// <exception cref="IOException">
1148		/// Will be thrown if file already exists and could not be overwritten or
1149		/// if creating the file failed for other reasons (e.g. unable to write).
1150		/// </exception>
1151		public static void CreateTextFile(string filename, string textForFile,
1152			Encoding encoding = null)
1153		{
1154			using (TextWriter textWriter = new StreamWriter(Open(
1155				filename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite),
1156				encoding ?? Encoding.UTF8))
1157			{
1158				/*this is nice, but slow, it should not be required anymore!
1159				string[] textLines = StringHelper.SplitMultiLineText(textForFile);
1160				foreach (string line in textLines)
1161				{
1162					textWriter.WriteLine(line);
1163				}
1164				 */
1165				textWriter.Write(textForFile);
1166				textWriter.Close();
1167			}
1168		}
1169
1170		/// <summary>
1171		/// Create text file. Will create a new file and store all the given text
1172		/// lines into it with the optional Encoding (default is UTF8).
1173		/// </summary>
1174		/// <param name="filename">Filename to save text to</param>
1175		/// <param name="textLines">Text lines to store</param>
1176		/// <param name="encoding">
1177		/// Encoding to use for file, by default UTF8
1178		/// </param>
1179		/// <exception cref="IOException">
1180		/// Will be thrown if file already exists and could not be overwritten or
1181		/// if creating the file failed for other reasons (e.g. unable to write).
1182		/// </exception>
1183		public static void CreateTextFile(string filename, string[] textLines,
1184			Encoding encoding = null)
1185		{
1186			using (StreamWriter textWriter = new StreamWriter(Open(
1187				filename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite),
1188				encoding ?? Encoding.UTF8))
1189			{
1190				foreach (string line in textLines)
1191				{
1192					textWriter.WriteLine(line);
1193				}
1194				textWriter.Close();
1195			}
1196		}
1197		#endregion
1198
1199		#region CreateBinaryFile (Static)
1200		/// <summary>
1201		/// Helper method to create a binary file with the given byte data array.
1202		/// /// <para />
1203		/// Note: If the file exists, it will just be overwritten. The path to
1204		/// the filename must exist however, else this will throw an exception.
1205		/// Also if writing this file is not possible an access denied IOException
1206		/// will be thrown.
1207		/// </summary>
1208		/// <param name="filename">Filename to save binary data to</param>
1209		/// <param name="binaryData">The data to save</param>
1210		public static void CreateBinaryFile(string filename, byte[] binaryData)
1211		{
1212			using (FileStream fileStream = Open(
1213				filename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
1214			{
1215				fileStream.Write(binaryData, 0, binaryData.Length);
1216			}
1217		}
1218
1219		/// <summary>
1220		/// Helper method to create a binary file with the given memory stream.
1221		/// <para />
1222		/// Note: If the file exists, it will just be overwritten. The path to
1223		/// the filename must exist however, else this will throw an exception.
1224		/// Also if writing this file is not possible an access denied IOException
1225		/// will be thrown.
1226		/// </summary>
1227		/// <param name="filename">Filename to save binary data to</param>
1228		/// <param name="data">The data to save</param>
1229		public static void CreateBinaryFile(string filename, MemoryStream data)
1230		{
1231			using (FileStream fileStream = Open(
1232				filename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
1233			{
1234				fileStream.Write(data.ToArray(), 0, (int)data.Length);
1235			}
1236		}
1237		#endregion
1238
1239		#region GetBytes (Static)
1240		/// <summary>
1241		/// Helper method to grab all bytes of a binary file and then close it
1242		/// again. Will not crash! If file does not exist, we will just return
1243		/// an empty array. If file is somehow unreadable, we will log an warning
1244		/// (because this shouldn't happen, we should always open files in a way
1245		/// that allows reading) and return an empty array too.
1246		/// </summary>
1247		/// <param name="filename">File to load</param>
1248		/// <returns>Array of bytes from the file</returns>
1249		public static byte[] GetBytes(string filename)
1250		{
1251			// If filename is invalid or file does not exist, just abort!
1252			if (String.IsNullOrEmpty(filename) ||
1253			    Exists(filename) == false)
1254			{
1255				Log.Warning("GetBytes failed to get non existing file: " + filename);
1256				return new byte[0];
1257			}
1258
1259			try
1260			{
1261				using (Stream fileStream = OpenFileAsStream(filename))
1262				{
1263					byte[] bytes = new byte[fileStream.Length];
1264					fileStream.Read(bytes, 0, bytes.Length);
1265					return bytes;
1266				}
1267			}
1268			catch (Exception ex)
1269			{
1270				Log.Warning("Failed to GetBytes of file '" + filename + "': " + ex);
1271				// Failed to read, just return an empty array!
1272				return new byte[0];
1273			}
1274		}
1275		#endregion
1276
1277		#region SafeDelete (Static)
1278		/// <summary>
1279		/// Safely deletes a file.
1280		/// </summary>
1281		/// <param name="filePath">File to delete</param>
1282		/// <returns>True if file was deleted or did not exist anymore</returns>
1283		public static bool SafeDelete(string filePath)
1284		{
1285			if (String.IsNullOrEmpty(filePath) ||
1286			    Exists(filePath) == false)
1287			{
1288				// File did not exist, so no deletion was required
1289				// (return true to mark that this file is gone now because it
1290				// was already gone).
1291				return true;
1292			}
1293
1294			try
1295			{
1296				DeleteCallback(filePath);
1297				return true;
1298			}
1299			catch (IOException ioEx)
1300			{
1301				if (ioEx.Message.Contains("The process cannot access the file"))
1302				{
1303					// Try to wait a short while, maybe it is free soon
1304					Thread.Sleep(50);
1305
1306					// And try to delete it again
1307					try
1308					{
1309						DeleteCallback(filePath);
1310						// Everything is fine now, quit without logging an error
1311						return true;
1312					}
1313					catch (Exception ex)
1314					{
1315						Log.Warning("Couldn't safely delete file even with waiting '" +
1316						            filePath + "' because of: " + ex.Message);
1317					}
1318				}
1319			}
1320			catch (Exception ex)
1321			{
1322				Log.Warning("Couldn't safely delete file '" + filePath +
1323				            "' because of: " + ex.Message);
1324			}
1325
1326			// File was not deleted
1327			return false;
1328		}
1329		#endregion
1330
1331		#region SafeDeleteFiles (Static)
1332		/// <summary>
1333		/// Safely delete the files matching the search pattern in the base
1334		/// directory and if wanted recursive in sub directories as well.
1335		/// </summary>
1336		/// <param name="basePath">Base Path</param>
1337		/// <param name="recursive">Recursive</param>
1338		/// <param name="searchPattern">Search Pattern</param>
1339		/// <returns>True if deleting the files worked (no matter if it actually
1340		/// deleted something or not), false otherwise (warnings for failed file
1341		/// deletes will be outputted via the SafeDelete method)</returns>
1342		public static bool SafeDeleteFiles(string basePath, string searchPattern,
1343			bool recursive)
1344		{
1345			if (DirectoryHelper.Exists(basePath) == false)
1346			{
1347				return true;
1348			}
1349
1350			string[] files = recursive
1351			                 	? DirectoryHelper.GetFilesRecursive(basePath, searchPattern)
1352			                 	: DirectoryHelper.GetFiles(basePath, searchPattern);
1353
1354			foreach (string file in files)
1355			{
1356				SafeDelete(file);
1357			}
1358
1359			return true;
1360		}
1361		#endregion
1362
1363		#region CheckIfFileIsNewer (Static)
1364		/// <summary>
1365		/// Check if file is newer. Not the fastest method ever as it needs
1366		/// to load both files and compare their last write times.
1367		/// </summary>
1368		/// <param name="fileToCheckIfNewer">File to check if newer</param>
1369		/// <param name="originalFile">Original File</param>
1370		/// <returns>True if the file is newer than the original.</returns>
1371		public static bool CheckIfFileIsNewer(string fileToCheckIfNewer,
1372			string originalFile)
1373		{
1374			// Got no file to check, then we can't compare and should not copy.
1375			if (Exists(fileToCheckIfNewer) == false)
1376			{
1377				return false;
1378			}
1379			// For no original file, the new file is obviously newer than nothing!
1380			if (Exists(originalFile) == false)
1381			{
1382				return true;
1383			}
1384
1385			return FileIsNewerCallback(fileToCheckIfNewer, originalFile);
1386		}
1387		#endregion
1388
1389		#region ExistsPath (Static)
1390		/// <summary>
1391		/// Exist path, will check if a full file path or just a directory path
1392		/// exists. This is different from the DirectoryHelper.Exists method,
1393		/// which just checks if a directory path exists (this one checks for
1394		/// files too if a valid extension for a file path was given).
1395		/// </summary>
1396		/// <param name="path">A path from a file or a directory</param>
1397		/// <returns>True if the path was found as a file or directory</returns>
1398		public static bool ExistsPath(string path)
1399		{
1400			if (Path.HasExtension(path))
1401			{
1402				return Exists(path);
1403			}
1404			return DirectoryHelper.Exists(path);
1405		}
1406		#endregion
1407
1408		#region CutPath (Static)
1409		/// <summary>
1410		/// Cuts off the given "cut path" completely from the given filePath.
1411		/// </summary>
1412		/// <param name="filePath">
1413		/// Any absolute or relative file or folder path.
1414		/// </param>
1415		/// <param name="pathToCutOff">
1416		/// The path (or folder) which should be cut off completely.
1417		/// </param>
1418		/// <returns>FilePath without pathToCutOff</returns>
1419		public static string CutPath(string filePath, string pathToCutOff)
1420		{
1421			#region Validation
1422			if (String.IsNullOrEmpty(filePath))
1423			{
1424				return "";
1425			}
1426			#endregion
1427
1428			// IF we have a valid path which should be cut off
1429			if (String.IsNullOrEmpty(pathToCutOff) == false)
1430			{
1431				// then try to find that (sub)path or folder in the given file path
1432				int cutOffIndex = filePath.IndexOf(pathToCutOff);
1433				// If it really exists in the file path
1434				if (cutOffIndex != MathHelper.InvalidIndex)
1435				{
1436					cutOffIndex += pathToCutOff.Length;
1437					// then just check if the "cut off path" ends already with an "\"
1438					if (pathToCutOff[pathToCutOff.Length - 1] != '\\')
1439					{
1440						// because if not then we have to increase the index by that
1441						cutOffIndex++;
1442					}
1443
1444					// before we can finally cut off the subpath, but only if we
1445					// don't will cut off a filename
1446					return (filePath[cutOffIndex - 1] != '.')
1447					       	? filePath.Substring(cutOffIndex)
1448					       	: filePath;
1449				}
1450			}
1451
1452			return filePath;
1453		}
1454		#endregion
1455
1456		#region CompareFiles (Static)
1457		/// <summary>
1458		/// Compare two files with the help of the MD5-checksum
1459		/// </summary>
1460		/// <param name="fileName1">Filename 1</param>
1461		/// <param name="fileName2">Filename 2</param>
1462		/// <returns>True if both file contents are the same</returns>
1463		public static bool CompareFiles(string fileName1, string fileName2)
1464		{
1465			return CompareFilesCallback(fileName1, fileName2);
1466		}
1467		#endregion
1468
1469		#region AppendTextToFile (Static)
1470		/// <summary>
1471		/// Append text to file, does the same as File.AppendAllText, but it
1472		/// won't crash and can handle already open files :)
1473		/// </summary>
1474		/// <param name="filename">Filename</param>
1475		/// <param name="text">Text</param>
1476		public static void AppendTextToFile(string filename, string text)
1477		{
1478			try
1479			{
1480				using (StreamWriter writer = new StreamWriter(Open(
1481					filename, FileMode.OpenOrCreate, FileAccess.Write,
1482					FileShare.ReadWrite), Encoding.UTF8))
1483				{
1484					// Go to end of file
1485					writer.BaseStream.Seek(0, SeekOrigin.End);
1486					// And write our new text
1487					writer.Write(text);
1488				}
1489			}
1490			catch (Exception ex)
1491			{
1492				Log.Warning("Failed to AppendTextToFile of file " + filename + ": " +
1493				            ex);
1494			}
1495		}
1496
1497		/// <summary>
1498		/// Append text to file, does the same as File.AppendAllText, but it
1499		/// won't crash and can handle already open files :) This overload checks
1500		/// if the existing data is more than maxTextFileSizeBeforeAppending
1501		/// already, then remove data from the beginning before appending.
1502		/// </summary>
1503		/// <param name="filename">Filename</param>
1504		/// <param name="maxTextFileSizeBeforeAppending">Max Text File Size before
1505		/// appending</param>
1506		/// <param name="text">Text</param>
1507		public static void AppendTextToFile(string filename, string text,
1508			int maxTextFileSizeBeforeAppending)
1509		{
1510			//this suxx: File.AppendAllText(filename, text, UTF8);
1511			try
1512			{
1513				FileStream stream = Open(filename, FileMode.OpenOrCreate,
1514					FileAccess.ReadWrite, FileShare.ReadWrite);
1515				// File is too big, remove data at the beginning (happens rarely).
1516				if (stream.Length > maxTextFileSizeBeforeAppending)
1517				{
1518					stream.Close();
1519					// Just read it all into lines and build it up again from bottom up!
1520					string[] oldLines = GetLines(filename, Encoding.UTF8);
1521					// Always grab the first 100 lines and keep them!
1522					List<string> newLines = new List<string>();
1523					int newSize = 0;
1524					for (int line = 0; line < 100 && line < oldLines.Length; line++)
1525					{
1526						string newLine = oldLines[line];
1527						newLines.Add(newLine);
1528						newSize += newLine.Length;
1529					}
1530					newLines.Add(
1531						"\n" +
1532						"****************************\n" +
1533						"This text file was exceeding " +
1534						StringHelper.WriteKbMbGbNumber(
1535							maxTextFileSizeBeforeAppending) + ", old data will be " +
1536						"removed from the beginning.\nSkipped the first 100 lines and " +
1537						"the new text is appended at the end as usually.\n" +
1538						"****************************\n");
1539					// Next fill up the lines until we reach the max size.
1540					int insertLineNum = newLines.Count;
1541					for (int line = oldLines.Length - 1; line >= 100; line--)
1542					{
1543						string newLine = oldLines[line];
1544						// Always insert at 101 (100 presevered lines + our message),
1545						// this way we go bottom up
1546						newLines.Insert(insertLineNum, newLine);
1547						newSize += newLine.Length;
1548						// Abort if we reach the max size / 2 (this way we don't have to
1549						// do this costly operation too often).
1550						if (newSize > maxTextFileSizeBeforeAppending / 2)
1551						{
1552							break;
1553						}
1554					}
1555
1556					// And finally create the new file and get another stream handle
1557					CreateTextFile(filename, newLines.ToArray(),
1558						Encoding.UTF8);
1559					stream = Open(filename, FileMode.OpenOrCreate,
1560						FileAccess.ReadWrite, FileShare.ReadWrite);
1561				}
1562
1563				using (StreamWriter writer = new StreamWriter(stream, Encoding.UTF8))
1564				{
1565					// Go to end of file
1566					writer.BaseStream.Seek(0, SeekOrigin.End);
1567					// And write our new text
1568					writer.Write(text);
1569				}
1570			}
1571			catch (Exception ex)
1572			{
1573				Log.Warning("Failed to AppendTextToFile of file " + filename + ": " +
1574				            ex);
1575			}
1576		}
1577		#endregion
1578
1579		#region CheckIfFileExistsAndIsNotWrittenTo (Static)
1580		/// <summary>
1581		/// Check if file exists and is not written to recently. This is mostly
1582		/// used for update checks to see if we can assume this file is complete
1583		/// and we can now open it, etc. (e.g. used in the RestartTool).
1584		/// </summary>
1585		/// <param name="filename">File to check</param>
1586		/// <returns>
1587		/// True if the file exists and was not written to recently.
1588		/// </returns>
1589		public static bool CheckIfFileExistsAndIsNotWrittenTo(string filename)
1590		{
1591			// First of all, make sure this file exists at all
1592			if (Exists(filename) == false)
1593			{
1594				return false;
1595			}
1596
1597			return FileNotWrittenToCallback(filename);
1598		}
1599		#endregion
1600
1601		#region OpenFileAsStream (Static)
1602		/// <summary>
1603		/// Open file as stream. Make sure the filePath is in the correct format
1604		/// for this platform!
1605		/// </summary>
1606		/// <param name="filePath">File path</param>
1607		/// <returns>Stream of the opened file for reading</returns>
1608		public static Stream OpenFileAsStream(string filePath)
1609		{
1610
1611			return Open(filePath, FileMode.Open, FileAccess.Read,
1612				FileShare.ReadWrite);
1613		}
1614		#endregion
1615
1616		#region Load (Static)
1617		/// <summary>
1618		/// Helper method to load data into any ISaveLoadBinary object just with
1619		/// a given filePath. This helps reducing writing the same code over and
1620		/// over again in each content class or ISaveLoadBinary data class.
1621		/// </summary>
1622		/// <typeparam name="T">Type to load into, must be derived from
1623		/// ISaveLoadBinary</typeparam>
1624		/// <param name="filePath">File path to load the binary data from</param>
1625		/// <param name="objectToLoad">Object to load all binary data into</param>
1626		public static void Load<T>(string filePath, T objectToLoad)
1627			where T : ISaveLoadBinary
1628		{
1629			using (Stream file = Open(filePath, FileMode.Open,
1630				FileAccess.Read, FileShare.Read))
1631			{
1632				objectToLoad.Load(new BinaryReader(file));
1633			}
1634		}
1635		#endregion
1636
1637		#region Save (Static)
1638		/// <summary>
1639		/// Helper method to save data from any ISaveLoadBinary object into a
1640		/// given filePath. This helps reducing writing the same code over and
1641		/// over again in each content class or ISaveLoadBinary data class.
1642		/// </summary>
1643		/// <typeparam name="T">Type to save into, must be derived from
1644		/// ISaveLoadBinary</typeparam>
1645		/// <param name="filePath">File path to save the binary data to</param>
1646		/// <param name="objectToSave">Object we want to save</param>
1647		public static void Save<T>(string filePath, T objectToSave)
1648			where T : ISaveLoadBinary
1649		{
1650			using (Stream file = Open(filePath, FileMode.Create,
1651				FileAccess.Write, FileShare.Read))
1652			{
1653				objectToSave.Save(new BinaryWriter(file));
1654			}
1655		}
1656		#endregion
1657
1658		#region GetFileDate (Static)
1659		/// <summary>
1660		/// Get file date via File.GetLastWriteTime if supported by this platform!
1661		/// </summary>
1662		/// <param name="filePath">File path to check</param>
1663		/// <returns>Last time this file was written to.</returns>
1664		public static DateTime GetFileDate(string filePath)
1665		{
1666			return File.GetLastWriteTime(filePath);
1667		}
1668		#endregion
1669
1670		#region Internal
1671
1672		#region ExistsCallback (Internal)
1673		internal static ExistsDelegate ExistsCallback;
1674		#endregion
1675
1676		#region CopyCallback (Internal)
1677		internal static CopyDelegate CopyCallback;
1678		#endregion
1679
1680		#region MoveCallback (Internal)
1681		internal static MoveDelegate MoveCallback;
1682		#endregion
1683
1684		#region OpenCallback (Internal)
1685		internal static OpenDelegate OpenCallback;
1686		#endregion
1687
1688		#region DeleteCallback (Internal)
1689		internal static DeleteDelegate DeleteCallback;
1690		#endregion
1691
1692		#region FileIsNewerCallback (Internal)
1693		internal static FileIsNewerDelegate FileIsNewerCallback;
1694		#endregion
1695
1696		#region CompareFilesCallback (Internal)
1697		/// <summary>
1698		/// Callback for comparing files. Some platforms won't support this as
1699		/// efficiently as others. Normally MD5 Checksums are used.
1700		/// </summary>
1701		internal static CompareFilesDelegate CompareFilesCallback;
1702		#endregion
1703
1704		#region FileNotWrittenToCallback (Internal)
1705		/// <summary>
1706		/// Delegate for CheckIfFileExistsAndIsNotWrittenTo.
1707		/// </summary>
1708		internal static FileNotWrittenToDelegate FileNotWrittenToCallback;
1709		#endregion
1710
1711		#endregion
1712
1713		#region Constructors
1714		/// <summary>
1715		/// Static FileHelper constructor to setup all the callbacks used here!
1716		/// </summary>
1717		static FileHelper()
1718		{
1719			ExistsCallback = File.Exists;
1720			MoveCallback = File.Move;
1721			OpenCallback = File.Open;
1722			DeleteCallback = File.Delete;
1723
1724			CopyCallback = delegate(string sourceFile, string destinationFile)
1725			{
1726				File.Copy(sourceFile, destinationFile, true);
1727			};
1728
1729			CompareFilesCallback = delegate(string fileName1, string fileName2)
1730			{
1731				MD5 md5 = new MD5CryptoServiceProvider();
1732
1733				byte[] md5Hash;
1734				// Compute checksum 1
1735				using (FileStream fileCheck = File.OpenRead(fileName1))
1736				{
1737					md5Hash = md5.ComputeHash(fileCheck);
1738				}
1739
1740				byte[] md5Hash2;
1741				// Now compute checksum 2
1742				using (FileStream fileCheck2 = File.OpenRead(fileName2))
1743				{
1744					md5Hash2 = md5.ComputeHash(fileCheck2);
1745				}
1746
1747				string checksum1 =
1748					BitConverter.ToString(md5Hash).Replace("-", "").ToLower();
1749				string checksum2 =
1750					BitConverter.ToString(md5Hash2).Replace("-", "").ToLower();
1751
1752				// Compare checksums
1753				return checksum1 == checksum2;
1754			};
1755
1756			FileIsNewerCallback = delegate(string fileToCheckIfNewer,
1757				string originalFile)
1758			{
1759				return
1760					File.GetLastWriteTime(fileToCheckIfNewer) >
1761					File.GetLastWriteTime(originalFile);
1762			};
1763
1764			FileNotWrittenToCallback = delegate(string filename)
1765			{
1766				DateTime now = DateTime.Now;
1767				// Next make sure this file is really written and completed
1768				// If there is still an FTP upload happening, we have to wait!
1769				if ((now - File.GetLastAccessTime(filename)).TotalSeconds < 1 ||
1770				    (now - File.GetLastWriteTime(filename)).TotalSeconds < 1)
1771				{
1772					// Wait a little longer, <1s since last write action is pretty short
1773					return false;
1774				}
1775
1776				// And finally check if we can write to the file. If another process
1777				// is writing to it, it wont allow us to write!
1778				try
1779				{
1780					using (FileStream testFile = Open(filename, FileMode.Open,
1781						FileAccess.ReadWrite, FileShare.ReadWrite))
1782					{
1783						// Nothing to do here, close the file again!
1784					}
1785					return true;
1786				}
1787				catch
1788				{
1789					// That failed, this file is still open in FTP or some program ..
1790					return false;
1791				}
1792			};
1793		}
1794		#endregion
1795
1796		/// <summary>
1797		/// Test file helper
1798		/// </summary>
1799		[Category("LongRunning")]
1800		internal class FileHelperTests
1801		{
1802			#region TestCreateTextFileAndSafeDelete (Static)
1803			/// <summary>
1804			/// Test create text file and safe delete
1805			/// </summary>
1806			[Test]
1807			public static void TestCreateTextFileAndSafeDelete()
1808			{
1809				const string TestFilename = "TestTextFile.txt";
1810				const string SomeText = "Just some text for the test file ...";
1811
1812				try
1813				{
1814					if (File.Exists(TestFilename))
1815					{
1816						SafeDelete(TestFilename);
1817						//throw new Exception("Unable to execute this unit test if " +
1818						//  TestFilename + " does already exists!");
1819					}
1820
1821					CreateTextFile(TestFilename, SomeText);
1822					// File contains now SomeText and "\n\r\0" characters.
1823					// Check if file size matches.
1824					Assert.Equal(SomeText.Length + 3,
1825						GetFileSize(TestFilename));
1826				}
1827				finally
1828				{
1829					// And now delete it safely again
1830					SafeDelete(TestFilename);
1831					// And make sure its gone which may take some millisecs on slow
1832					// hardware!
1833					Thread.Sleep(10);
1834					// There should be nothing left now
1835					Assert.False(File.Exists(TestFilename), "File exists");
1836				}
1837			}
1838			#endregion
1839
1840			#region TestCompareFiles (Static)
1841			/// <summary>
1842			/// Test check if file is newer. Note: Too slow for a dynamic unit test.
1843			/// </summary>
1844			[Test]
1845			public static void TestCompareFiles()
1846			{
1847				CreateTextFile("test1.txt", "Hello world!");
1848				CreateTextFile("test2.txt", "Hello!");
1849
1850				Assert.True(CompareFiles("test1.txt", "test1.txt"));
1851				Assert.False(CompareFiles("test1.txt", "test2.txt"));
1852
1853				SafeDelete("test1.txt");
1854				SafeDelete("test2.txt");
1855			}
1856			#endregion
1857
1858			#region TestPathCombine (Static)
1859			/// <summary>
1860			/// Test to see if our path combine methods correctly output the right path
1861			/// </summary>
1862			[Test]
1863			public static void TestPathCombine()
1864			{
1865				Assert.Equal(PathCombine("path1", "path2"), "path1/path2");
1866				Assert.Equal(PathCombine("path1", "/path2"), "path1/path2");
1867				Assert.Equal(PathCombine(@"path1\", "/path2"), "path1/path2");
1868				Assert.Equal(PathCombine(@"path1\", @"\path2"), "path1/path2");
1869				Assert.Equal(PathCombine(".path1/", "/path2."), "path1/path2");
1870				Assert.Equal(PathCombine(@"C:\path1", "/path2"), "C:/path1/path2");
1871			}
1872			#endregion
1873
1874			#region OpenFileAsStream (Static)
1875			/// <summary>
1876			/// Test OpenFileAsStream
1877			/// </summary>
1878			[Test]
1879			public static void OpenFileAsStream()
1880			{
1881				const string filepath = @"C:\Windows\winhlp32.exe";
1882				Stream stream = FileHelper.OpenFileAsStream(filepath);
1883				Assert.Equal(GetFileSize(filepath), stream.Length);
1884				stream.Close();
1885				stream = null;
1886			}
1887			#endregion
1888
1889			#region SafeDeleteFiles (Static)
1890			/// <summary>
1891			/// Test SafeDeleteFiles with some xml documentation files in the
1892			/// current directory.
1893			/// </summary>
1894			[Test]
1895			public static void SafeDeleteFiles()
1896			{
1897				FileHelper.SafeDeleteFiles(
1898					DirectoryHelper.GetCurrentDirectory(), "Delta.*.xml", true);
1899			}
1900			#endregion
1901
1902			#region TrimPath (Static)
1903			/// <summary>
1904			/// Trim path
1905			/// </summary>
1906			[Test]
1907			public static void TrimPath()
1908			{
1909				// Existing file path
1910				Assert.Equal(FileHelper.TrimPath(
1911					@"C:\Windows\Temp\..\System32\notepad.exe"),
1912					@"C:\Windows\System32\notepad.exe");
1913
1914				// Fictional absolute file path
1915				Assert.Equal(FileHelper.TrimPath(
1916					@"C:\Path\SubPath1\..\SubPath2\file.exe"),
1917					@"C:\Path\SubPath2\file.exe");
1918
1919				// Fictional relative file path
1920				Assert.Equal(FileHelper.TrimPath(
1921					@"SubPath1\..\SubPath2\file.exe"),
1922					@"SubPath2\file.exe");
1923
1924				// Test with network path
1925				Assert.Equal(FileHelper.TrimPath(
1926					@"\\blub\Bla\Honk\..\..\ContentSystem\Delta.ContentSystem.csproj"),
1927					@"\\blub\ContentSystem\Delta.ContentSystem.csproj");
1928
1929				// Test unix path and also add a trailing slash into the mix!
1930				Assert.Equal(FileHelper.TrimPath(
1931					"/SomePath/../SomeFile.xml"),
1932					"SomeFile.xml");
1933
1934				// Trying to trim a part away without providing a full path should not
1935				// kill the relative structure. We just want to trim after all.
1936				Assert.Equal(FileHelper.TrimPath(
1937					@"..\..\..\file.exe"),
1938					@"..\..\..\file.exe");
1939
1940				// Going all the way down 4 levels.
1941				Assert.Equal(FileHelper.TrimPath(
1942					@"C:\SomePath\AnotherPath\ThirdPath\ForthPath\..\..\..\..\file.exe"),
1943					@"C:\file.exe");
1944
1945				// Do the same with a relative path, we should not get an absolute path
1946				// back. Note: Very long path to test if converting the absolute path
1947				// to a relative one actually works.
1948				Assert.Equal(FileHelper.TrimPath(
1949					@"SomePath\AnotherPath\ThirdParth\ForthPath\..\..\..\..\file.exe"),
1950					@"file.exe");
1951			}
1952			#endregion
1953
1954			#region CleanupWindowsPath
1955			[Test]
1956			public static void CleanupWindowsPath()
1957			{
1958				// Test windows paths
1959				Assert.Equal(FileHelper.CleanupWindowsPath(
1960					@"C:\Windows\System32\notepad.exe"),
1961					@"C:\Windows\System32\notepad.exe");
1962				Assert.Equal(FileHelper.CleanupWindowsPath(
1963					@"SomePath/SomeFile.xml"),
1964					@"SomePath\SomeFile.xml");
1965			}
1966			#endregion
1967
1968			#region CleanupLinuxPath
1969			[Test]
1970			public static void CleanupLinuxPath()
1971			{
1972				// And do the same with Linux paths
1973				Assert.Equal(FileHelper.CleanupLinuxPath(
1974					@"C:\Windows\System32\notepad.exe"),
1975					@"C:/Windows/System32/notepad.exe");
1976				Assert.Equal(FileHelper.CleanupLinuxPath(
1977					@"SomePath/SomeFile.xml"),
1978					@"SomePath/SomeFile.xml");
1979			}
1980			#endregion
1981
1982			#region FileExists (Static)
1983			/// <summary>
1984			/// Test the File Exists method.
1985			/// </summary>
1986			[Test]
1987			public static void FileExists()
1988			{
1989				Assert.True(Exists("C:\\Windows\\regedit.exe"));
1990				Assert.False(Exists("C:\\awdawdok.test"));
1991			}
1992			#endregion
1993
1994			#region TestCutAndGetExtension
1995			/// <summary>
1996			/// Test cut and get extension
1997			/// </summary>
1998			[Test]
1999			public void TestCutAndGetExtension()
2000			{
2001				// Test cut extension, it should only remove extensions, nothing more.
2002				Assert.Equal("hi", CutExtension("hi.txt"));
2003				Assert.Equal("hi", CutExtension("hi"));
2004				Assert.Equal("SomeApplication.exe",
2005					CutExtension("SomeApplication.exe.config"));
2006				Assert.Equal(@"..\..\Yaya\Honk",
2007					CutExtension(@"..\..\Yaya\Honk.exe"));
2008				Assert.Equal(@"..\..\Yaya\Honk",
2009					CutExtension(@"..\..\Yaya\Honk"));
2010
2011				// Test get extension, should return the extension the file has.
2012				// Should work the same way as CutExtension.
2013				Assert.Equal("txt", GetExtension("hi.txt"));
2014				Assert.Equal("", GetExtension("hi"));
2015				Assert.Equal("config",
2016					GetExtension("SomeApplication.exe.config"));
2017				Assert.Equal("exe", GetExtension(@"..\..\Yaya\Honk.exe"));
2018			}
2019			#endregion
2020
2021			#region GetFilename
2022			/// <summary>
2023			/// Get filename
2024			/// </summary>
2025			[Test]
2026			public void GetFilename()
2027			{
2028				Assert.Equal("File.tst",
2029					FileHelper.GetFilename(@"Path\SubPath\File.tst"));
2030
2031				Assert.Equal("File.tst",
2032					FileHelper.GetFilename(@"Path\SubPath/File.tst"));
2033
2034				Assert.Equal("File",
2035					FileHelper.GetFilename(@"Path\SubPath\File.tst", true));
2036
2037				string pathOnly;
2038				Assert.Equal("File.tst",
2039					FileHelper.GetFilename(@"Path\SubPath\File.tst", out pathOnly));
2040				Assert.Equal(@"Path\SubPath", pathOnly);
2041			}
2042			#endregion
2043
2044			#region IsFilenameOnly
2045			/// <summary>
2046			/// Is filename only
2047			/// </summary>
2048			[Test]
2049			public void IsFilenameOnly()
2050			{
2051				Assert.True(FileHelper.IsFilenameOnly("File123.tst"));
2052				Assert.False(FileHelper.IsFilenameOnly("Folder"));
2053			}
2054			#endregion
2055
2056			#region TestCheckIfFileIsNewer
2057			/// <summary>
2058			/// Test check if file is newer
2059			/// </summary>
2060			[Test]
2061			public void TestCheckIfFileIsNewer()
2062			{
2063				//does not work this way, a full rebuild changes the dates around :(
2064				//// Delta.Utilities.Helpers.dll is the newest file of all, because we
2065				//// just compiled it to start this test. Delta.Utilities.Testing.dll
2066				//// doesn't change that often (and even if, it was compiled before).
2067				//Assert.True(FileHelper.CheckIfFileIsNewer(
2068				//  "Delta.Utilities.Helpers.dll", "Delta.Utilities.Testing.dll"));
2069				//Assert.False(FileHelper.CheckIfFileIsNewer(
2070				//  "Delta.Utilities.Testing.dll", "Delta.U