/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
Large files files are truncated, but you can click here to view the full file
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, …
Large files files are truncated, but you can click here to view the full file