FileSystem.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. using Microsoft.Win32.SafeHandles;
  2. using System;
  3. using System.ComponentModel;
  4. using System.Globalization;
  5. using System.IO;
  6. using System.Runtime.InteropServices;
  7. using System.Windows.Forms;
  8. namespace PaintDotNet.SystemLayer
  9. {
  10. public static class FileSystem
  11. {
  12. private const string sessionLockFileName = "session.lock";
  13. private static string tempDir;
  14. private static Stream sessionToken;
  15. private static Random random = new Random();
  16. /// <summary>
  17. /// Creates a file stream with the given filename that is deleted when either the
  18. /// stream is closed, or the application is terminated.
  19. /// </summary>
  20. /// <remarks>
  21. /// If the file already exists, it is overwritten without any error (CREATE_ALWAYS).
  22. /// </remarks>
  23. /// <param name="fileName">The full path to the file to create.</param>
  24. /// <returns>A Stream with read and write access.</returns>
  25. public static FileStream CreateTempFile(string fileName)
  26. {
  27. IntPtr hFile = SafeNativeMethods.CreateFileW(
  28. fileName,
  29. NativeConstants.GENERIC_READ | NativeConstants.GENERIC_WRITE,
  30. NativeConstants.FILE_SHARE_READ,
  31. IntPtr.Zero,
  32. NativeConstants.CREATE_ALWAYS,
  33. NativeConstants.FILE_ATTRIBUTE_TEMPORARY |
  34. NativeConstants.FILE_FLAG_DELETE_ON_CLOSE |
  35. NativeConstants.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
  36. IntPtr.Zero);
  37. if (hFile == NativeConstants.INVALID_HANDLE_VALUE)
  38. {
  39. NativeMethods.ThrowOnWin32Error("CreateFileW returned INVALID_HANDLE_VALUE");
  40. }
  41. SafeFileHandle sfhFile = new SafeFileHandle(hFile, true);
  42. FileStream stream;
  43. try
  44. {
  45. stream = new FileStream(sfhFile, FileAccess.ReadWrite);
  46. }
  47. catch (Exception)
  48. {
  49. SafeNativeMethods.CloseHandle(hFile);
  50. hFile = IntPtr.Zero;
  51. throw;
  52. }
  53. return stream;
  54. }
  55. /// <summary>
  56. /// Opens a file for streaming. This stream should be read from or written
  57. /// to sequentially for best performance. Random I/O is still permissible,
  58. /// but may not perform as well.
  59. /// This file is created in such a way that is it NOT indexed by the system's
  60. /// file indexer (e.g., Windows Desktop Search).
  61. /// </summary>
  62. /// <param name="fileName">The file to open.</param>
  63. /// <returns>A Stream object that may be used to read from or write to the file, depending on the fileMode parameter.</returns>
  64. public static FileStream OpenStreamingFile(string fileName, FileAccess fileAccess)
  65. {
  66. uint dwDesiredAccess;
  67. uint dwCreationDisposition;
  68. switch (fileAccess)
  69. {
  70. case FileAccess.Read:
  71. dwDesiredAccess = NativeConstants.GENERIC_READ;
  72. dwCreationDisposition = NativeConstants.OPEN_EXISTING;
  73. break;
  74. case FileAccess.ReadWrite:
  75. dwDesiredAccess = NativeConstants.GENERIC_READ | NativeConstants.GENERIC_WRITE;
  76. dwCreationDisposition = NativeConstants.OPEN_ALWAYS;
  77. break;
  78. case FileAccess.Write:
  79. dwDesiredAccess = NativeConstants.GENERIC_WRITE;
  80. dwCreationDisposition = NativeConstants.CREATE_NEW;
  81. break;
  82. default:
  83. throw new InvalidEnumArgumentException();
  84. }
  85. uint dwFlagsAndAttributes =
  86. NativeConstants.FILE_ATTRIBUTE_TEMPORARY |
  87. NativeConstants.FILE_FLAG_SEQUENTIAL_SCAN |
  88. NativeConstants.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
  89. IntPtr hFile = SafeNativeMethods.CreateFileW(
  90. fileName,
  91. dwDesiredAccess,
  92. NativeConstants.FILE_SHARE_READ,
  93. IntPtr.Zero,
  94. dwCreationDisposition,
  95. dwFlagsAndAttributes,
  96. IntPtr.Zero);
  97. if (hFile == NativeConstants.INVALID_HANDLE_VALUE)
  98. {
  99. NativeMethods.ThrowOnWin32Error("CreateFileW returned INVALID_HANDLE_VALUE");
  100. }
  101. FileStream stream;
  102. try
  103. {
  104. SafeFileHandle sfh = new SafeFileHandle(hFile, true);
  105. stream = new FileStream(sfh, fileAccess, 512, false);
  106. }
  107. catch
  108. {
  109. SafeNativeMethods.CloseHandle(hFile);
  110. hFile = IntPtr.Zero;
  111. throw;
  112. }
  113. return stream;
  114. }
  115. /// <summary>
  116. /// Writes the given bytes to a stream.
  117. /// </summary>
  118. /// <param name="output">The stream to write data to.</param>
  119. /// <param name="pvBits">A pointer to the data to write.</param>
  120. /// <param name="length">The number of bytes to write.</param>
  121. /// <remarks>
  122. /// This method is provided for performance (memory-usage) purposes. It relies on
  123. /// the fact that FileStream provides a property for retrieving the Win32 file
  124. /// handle.
  125. /// </remarks>
  126. [CLSCompliant(false)]
  127. public unsafe static void WriteToStream(FileStream output, void* pvBuffer, uint length)
  128. {
  129. IntPtr hFile = output.SafeFileHandle.DangerousGetHandle();
  130. WriteToStream(hFile, pvBuffer, length);
  131. GC.KeepAlive(output);
  132. }
  133. private unsafe static void WriteToStream(IntPtr hFile, void* pvBuffer, uint length)
  134. {
  135. if (hFile == NativeConstants.INVALID_HANDLE_VALUE)
  136. {
  137. throw new ArgumentException("output", "File is closed");
  138. }
  139. void* pvWrite = pvBuffer;
  140. while (length > 0)
  141. {
  142. uint written;
  143. bool result = SafeNativeMethods.WriteFile(hFile, pvWrite, length, out written, IntPtr.Zero);
  144. if (!result)
  145. {
  146. NativeMethods.ThrowOnWin32Error("WriteFile() returned false");
  147. }
  148. pvWrite = (void*)((byte*)pvWrite + written);
  149. length -= written;
  150. }
  151. }
  152. /*
  153. private unsafe static void WriteToStreamingFileGatherAsync(IntPtr hFile, void *[] ppvBuffers, uint[] lengths)
  154. {
  155. bool result = true;
  156. uint dwResult = NativeConstants.ERROR_SUCCESS;
  157. long totalBytes = 0;
  158. // Compute total amount of bytes they want to write
  159. for (int i = 0; i < lengths.Length; ++i)
  160. {
  161. totalBytes += (long)lengths[i];
  162. }
  163. // Resize the file to match how much they're writing to it
  164. ulong newFilePointer;
  165. result = SafeNativeMethods.SetFilePointerEx(hFile, (ulong)totalBytes, out newFilePointer, NativeConstants.FILE_BEGIN);
  166. if (!result)
  167. {
  168. NativeMethods.ThrowOnWin32Error("SetFilePointerEx returned false (1)");
  169. }
  170. result = SafeNativeMethods.SetEndOfFile(hFile);
  171. if (!result)
  172. {
  173. NativeMethods.ThrowOnWin32Error("SetEndOfFile returned false");
  174. }
  175. result = SafeNativeMethods.SetFilePointerEx(hFile, 0, out newFilePointer, NativeConstants.FILE_BEGIN);
  176. if (!result)
  177. {
  178. NativeMethods.ThrowOnWin32Error("SetFilePointerEx returned false (2)");
  179. }
  180. // Method 2 -- buffered
  181. const uint bufferSize = 8192; // increasing this to 65536 or 131072 did not seem to affect performance for dumping out very large images (190mb), so we'll err on the side of using less memory
  182. IntPtr pBuffer1 = IntPtr.Zero;
  183. IntPtr pBuffer2 = IntPtr.Zero;
  184. IntPtr hEvent = IntPtr.Zero;
  185. ulong position = 0;
  186. try
  187. {
  188. NativeStructs.OVERLAPPED overlapped = new NativeStructs.OVERLAPPED();
  189. hEvent = SafeNativeMethods.CreateEventW(IntPtr.Zero, true, true, null);
  190. if (hEvent == IntPtr.Zero)
  191. {
  192. NativeMethods.ThrowOnWin32Error("CreateEventW returned false");
  193. }
  194. overlapped.hEvent = hEvent;
  195. pBuffer1 = Memory.AllocateLarge((ulong)bufferSize);
  196. pBuffer2 = Memory.AllocateLarge((ulong)bufferSize);
  197. byte *pBufferBytes1 = (byte *)pBuffer1.ToPointer();
  198. byte *pBufferBytes2 = (byte *)pBuffer2.ToPointer();
  199. uint writeCursor = 0;
  200. for (int i = 0; i < ppvBuffers.Length; ++i)
  201. {
  202. uint readCursor = 0;
  203. while (readCursor < lengths[i])
  204. {
  205. uint bytesToCopy = Math.Min(lengths[i] - readCursor, bufferSize - writeCursor);
  206. Memory.Copy((void *)(pBufferBytes1 + writeCursor), (void *)((byte *)ppvBuffers[i] + readCursor),
  207. (ulong)bytesToCopy);
  208. writeCursor += bytesToCopy;
  209. readCursor += bytesToCopy;
  210. // If we filled the write buffer, OR if this it the very last block to write
  211. if (writeCursor == bufferSize || (i == ppvBuffers.Length - 1 && readCursor == lengths[i]))
  212. {
  213. // Wait for the previous I/O to finish
  214. dwResult = SafeNativeMethods.WaitForSingleObject(hEvent, NativeConstants.INFINITE);
  215. if (dwResult != NativeConstants.WAIT_OBJECT_0)
  216. {
  217. NativeMethods.ThrowOnWin32Error("WaitForSingleObject did not return WAIT_OBJECT_0");
  218. }
  219. // Set up the new I/O
  220. overlapped.Offset = (uint)(position & 0xffffffff);
  221. overlapped.OffsetHigh = (uint)(position >> 32);
  222. uint dwBytesWritten;
  223. result = SafeNativeMethods.WriteFile(
  224. hFile,
  225. pBufferBytes1,
  226. writeCursor,
  227. out dwBytesWritten,
  228. ref overlapped);
  229. if (!result)
  230. {
  231. int error = Marshal.GetLastWin32Error();
  232. if (error != NativeConstants.ERROR_IO_PENDING)
  233. {
  234. throw new Win32Exception(error, "WriteFile returned false and GetLastError() did not return ERROR_IO_PENDING");
  235. }
  236. }
  237. // Adjust cursors and swap buffers
  238. position += (ulong)bufferSize;
  239. byte *temp = pBufferBytes1;
  240. pBufferBytes1 = pBufferBytes2;
  241. pBufferBytes2 = temp;
  242. writeCursor = 0;
  243. }
  244. }
  245. }
  246. // Flush remaining data by waiting for the previous I/O to finish
  247. dwResult = SafeNativeMethods.WaitForSingleObject(hEvent, NativeConstants.INFINITE);
  248. if (dwResult != NativeConstants.WAIT_OBJECT_0)
  249. {
  250. NativeMethods.ThrowOnWin32Error("WaitForSingleObject did not return WAIT_OBJECT_0");
  251. }
  252. }
  253. finally
  254. {
  255. if (pBuffer1 != IntPtr.Zero)
  256. {
  257. Memory.FreeLarge(pBuffer1);
  258. pBuffer1 = IntPtr.Zero;
  259. }
  260. if (pBuffer2 != IntPtr.Zero)
  261. {
  262. Memory.FreeLarge(pBuffer2);
  263. pBuffer2 = IntPtr.Zero;
  264. }
  265. if (hEvent != IntPtr.Zero)
  266. {
  267. result = SafeNativeMethods.CloseHandle(hEvent);
  268. if (!result)
  269. {
  270. NativeMethods.ThrowOnWin32Error("CloseHandle returned false on hEvent");
  271. }
  272. }
  273. }
  274. }
  275. * */
  276. /// <summary>
  277. /// Writes data to the file. This data may be scattered throughout memory, but is written contiguously
  278. /// to the file such that ppvBuffers[n][m] is written to file location m + summation of lengths[0 through n - 1].
  279. /// If n is 0, then ppvBuffers[0][m] is written to file location m.
  280. /// Or, in pseudo code:
  281. /// for (int n = 0; n &lt; lengths.Length; ++n)
  282. /// {
  283. /// for (int m = 0; m &lt; lengths[n]; ++m)
  284. /// {
  285. /// WriteByte(outputHandle, ppvBuffers[n][m]);
  286. /// }
  287. /// }
  288. /// </summary>
  289. /// <param name="outputHandle">The stream to write to.</param>
  290. /// <param name="ppvBuffers">Pointers to buffers to write from.</param>
  291. /// <param name="lengths">The lengths of each buffer.</param>
  292. /// <remarks>
  293. /// ppvBuffers.Length must equal lengths.Length
  294. /// </remarks>
  295. public unsafe static void WriteToStreamingFileGather(FileStream outputStream, void*[] ppvBuffers, uint[] lengths)
  296. {
  297. if (ppvBuffers.Length != lengths.Length)
  298. {
  299. throw new ArgumentException("ppvBuffers.Length != lengths.Length");
  300. }
  301. if (!outputStream.CanWrite)
  302. {
  303. throw new ArgumentException("outputStream.CanWrite == false");
  304. }
  305. IntPtr hFile = outputStream.SafeFileHandle.DangerousGetHandle();
  306. for (int i = 0; i < ppvBuffers.Length; ++i)
  307. {
  308. WriteToStream(hFile, ppvBuffers[i], lengths[i]);
  309. }
  310. GC.KeepAlive(outputStream);
  311. }
  312. /// <summary>
  313. /// Reads data from the file. This data is read contiguously from the file, but the buffers may
  314. /// be scattered throughout memory such that ppvBuffers[n][m] is read from file location
  315. /// m + summation of lengths[0 through n - 1]. If n is 0, then ppvBuffers[0][m] is read from
  316. /// file location m.
  317. /// Or, in pseudo code:
  318. /// for (int n = 0; n &lt; lengths.Length; ++n)
  319. /// {
  320. /// for (int m = 0; m &lt; lengths[n]; ++m)
  321. /// {
  322. /// ppvBuffers[n][m] = ReadByte(input);
  323. /// }
  324. /// }
  325. /// </summary>
  326. /// <param name="input"></param>
  327. /// <param name="ppvBuffers"></param>
  328. /// <param name="lengths"></param>
  329. /// <remarks>This method is the counter to WriteToStreamingFileGather. ppvBuffers.Length must equal lengths.Length.</remarks>
  330. public unsafe static void ReadFromStreamScatter(FileStream input, void*[] ppvBuffers, uint[] lengths)
  331. {
  332. if (ppvBuffers.Length != lengths.Length)
  333. {
  334. throw new ArgumentException("ppvBuffers.Length != lengths.Length");
  335. }
  336. for (int i = 0; i < ppvBuffers.Length; ++i)
  337. {
  338. if (lengths[i] > 0)
  339. {
  340. ReadFromStream(input, ppvBuffers[i], lengths[i]);
  341. }
  342. }
  343. }
  344. /// <summary>
  345. /// Reads bytes from a stream.
  346. /// </summary>
  347. /// <param name="output"></param>
  348. /// <param name="pvBits"></param>
  349. /// <param name="length"></param>
  350. /// <remarks>
  351. /// This method is provided for performance (memory-usage) purposes.
  352. /// </remarks>
  353. [CLSCompliant(false)]
  354. public unsafe static void ReadFromStream(FileStream input, void* pvBuffer, uint length)
  355. {
  356. SafeFileHandle sfhFile = input.SafeFileHandle;
  357. if (sfhFile.IsInvalid)
  358. {
  359. throw new ArgumentException("input", "File is closed");
  360. }
  361. void* pvRead = pvBuffer;
  362. while (length > 0)
  363. {
  364. uint read;
  365. bool result = SafeNativeMethods.ReadFile(sfhFile, pvRead, length, out read, IntPtr.Zero);
  366. if (!result)
  367. {
  368. NativeMethods.ThrowOnWin32Error("ReadFile() returned false");
  369. }
  370. if (result && read == 0)
  371. {
  372. throw new EndOfStreamException();
  373. }
  374. pvRead = (void*)((byte*)pvRead + read);
  375. length -= read;
  376. }
  377. GC.KeepAlive(input);
  378. }
  379. static FileSystem()
  380. {
  381. // Determine root path of where we store our persisted data
  382. string localSettingsDir = Shell.GetVirtualPath(VirtualFolderName.UserLocalAppData, true);
  383. string tempDirRoot = Path.Combine(localSettingsDir, "Metis Vision");
  384. DirectoryInfo tempDirRootInfo = new DirectoryInfo(tempDirRoot);
  385. if (!tempDirRootInfo.Exists)
  386. {
  387. tempDirRootInfo.Create();
  388. }
  389. // Clean up old session data
  390. string[] oldDirPaths = Directory.GetDirectories(tempDirRoot);
  391. foreach (string oldDirPath in oldDirPaths)
  392. {
  393. bool cleanUp = false;
  394. string lockPath = Path.Combine(oldDirPath, sessionLockFileName);
  395. // If the file doesn't exists, then clean up
  396. if (!cleanUp)
  397. {
  398. FileInfo lockFileInfo = new FileInfo(lockPath);
  399. if (!lockFileInfo.Exists)
  400. {
  401. cleanUp = true;
  402. }
  403. }
  404. // If we can delete the lock file, then definitely clean up
  405. if (!cleanUp)
  406. {
  407. cleanUp = FileSystem.TryDeleteFile(lockPath);
  408. }
  409. if (cleanUp)
  410. {
  411. string[] filesToCleanUp = Directory.GetFiles(oldDirPath, "*");
  412. foreach (string fileToCleanUp in filesToCleanUp)
  413. {
  414. bool result1 = TryDeleteFile(fileToCleanUp);
  415. }
  416. FileSystem.TryDeleteDirectory(oldDirPath);
  417. }
  418. }
  419. // Determine the directory where we will store this session's data
  420. while (true)
  421. {
  422. int subDirOrdinal = random.Next();
  423. string subDir = Path.Combine(tempDirRoot, subDirOrdinal.ToString(CultureInfo.InvariantCulture));
  424. DirectoryInfo dirInfo = new DirectoryInfo(subDir);
  425. if (!dirInfo.Exists)
  426. {
  427. dirInfo.Create();
  428. tempDir = subDir;
  429. EnableCompression(tempDir);
  430. break;
  431. }
  432. }
  433. // Create our session lock cookie -- this file is locked for our process' lifetime
  434. // If our process is terminated, the file is deleted but our session files are not
  435. // However, if the system is abnormally shut down, neither the session lock file nor
  436. // the session temp files are deleted.
  437. string sessionTokenPath = Path.Combine(tempDir, sessionLockFileName);
  438. sessionToken = FileSystem.CreateTempFile(sessionTokenPath);
  439. // Cleanup when the app exits.
  440. Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
  441. }
  442. public static bool TryDeleteFile(string filePath)
  443. {
  444. return NativeMethods.DeleteFileW(filePath);
  445. }
  446. public static bool TryDeleteFile(string dirPath, string fileName)
  447. {
  448. return Do.TryBool(() =>
  449. Do.GenerateTest(() =>
  450. Path.Combine(dirPath, fileName),
  451. s => File.Exists(s),
  452. s => TryDeleteFile(s),
  453. s => { }));
  454. }
  455. public static bool TryDeleteDirectory(string dirPath)
  456. {
  457. return NativeMethods.RemoveDirectoryW(dirPath);
  458. }
  459. private static bool EnableCompression(string filePath)
  460. {
  461. IntPtr hFile = IntPtr.Zero;
  462. try
  463. {
  464. hFile = SafeNativeMethods.CreateFileW(
  465. filePath,
  466. NativeConstants.GENERIC_READ | NativeConstants.GENERIC_WRITE,
  467. NativeConstants.FILE_SHARE_READ | NativeConstants.FILE_SHARE_WRITE | NativeConstants.FILE_SHARE_DELETE,
  468. IntPtr.Zero,
  469. NativeConstants.OPEN_EXISTING,
  470. NativeConstants.FILE_FLAG_BACKUP_SEMANTICS,
  471. IntPtr.Zero);
  472. if (hFile == NativeConstants.INVALID_HANDLE_VALUE)
  473. {
  474. int dwError = Marshal.GetLastWin32Error();
  475. return false;
  476. }
  477. ushort cType = NativeConstants.COMPRESSION_FORMAT_DEFAULT;
  478. uint dwBytes = 0;
  479. bool bResult;
  480. unsafe
  481. {
  482. bResult = NativeMethods.DeviceIoControl(
  483. hFile,
  484. NativeConstants.FSCTL_SET_COMPRESSION,
  485. new IntPtr(&cType),
  486. sizeof(ushort),
  487. IntPtr.Zero,
  488. 0,
  489. ref dwBytes,
  490. IntPtr.Zero);
  491. }
  492. return bResult;
  493. }
  494. finally
  495. {
  496. if (hFile != IntPtr.Zero)
  497. {
  498. SafeNativeMethods.CloseHandle(hFile);
  499. hFile = IntPtr.Zero;
  500. }
  501. }
  502. }
  503. private static void Application_ApplicationExit(object sender, EventArgs e)
  504. {
  505. if (sessionToken != null)
  506. {
  507. sessionToken.Close();
  508. sessionToken = null;
  509. }
  510. }
  511. /// <summary>
  512. /// Generates a random filename for a file in the app's per-user temporary directory.
  513. /// </summary>
  514. /// <returns>
  515. /// The full path for a temporary filename. The file does not exist at the time this method returns.
  516. /// </returns>
  517. public static string GetTempFileName()
  518. {
  519. string returnPath;
  520. while (true)
  521. {
  522. int ordinal = random.Next();
  523. string path = Path.Combine(tempDir, ordinal.ToString(CultureInfo.InvariantCulture));
  524. FileInfo fileInfo = new FileInfo(path);
  525. if (!fileInfo.Exists)
  526. {
  527. returnPath = path;
  528. break;
  529. }
  530. }
  531. return returnPath;
  532. }
  533. public static string GetTempPathName(string fileName)
  534. {
  535. string fileName2 = Path.GetFileName(fileName);
  536. string pathName = Path.Combine(tempDir, fileName2);
  537. return pathName;
  538. }
  539. }
  540. }