PageRenderTime 30ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

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