PageRenderTime 58ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Castle.Services.Transaction/FileTransactions/FileTransaction.cs

https://github.com/gusgorman/Castle.Services.Transaction
C# | 1093 lines | 592 code | 158 blank | 343 comment | 73 complexity | e5e0c6f0d01d3e513c5ef3a11973d5e8 MD5 | raw file
  1. #region License
  2. // Copyright 2004-2010 Castle Project - http://www.castleproject.org/
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. #endregion
  17. namespace Castle.Services.Transaction
  18. {
  19. using System;
  20. using System.Collections.Generic;
  21. using System.ComponentModel;
  22. using System.IO;
  23. using System.Linq;
  24. using System.Runtime.InteropServices;
  25. using System.Security.Permissions;
  26. using System.Text;
  27. using System.Transactions;
  28. using IO;
  29. using Microsoft.Win32.SafeHandles;
  30. using Path = IO.Path;
  31. ///<summary>
  32. /// Represents a transaction on transactional kernels
  33. /// like the Vista kernel or Server 2008 kernel and newer.
  34. ///</summary>
  35. /// <remarks>
  36. /// Good information for dealing with the peculiarities of the runtime:
  37. /// http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.safehandle.aspx
  38. /// </remarks>
  39. public sealed class FileTransaction : TransactionBase, IFileTransaction
  40. {
  41. private SafeTxHandle _TransactionHandle;
  42. private bool _Disposed;
  43. #region Constructors
  44. ///<summary>
  45. /// c'tor w/o name.
  46. ///</summary>
  47. public FileTransaction() : this(null)
  48. {
  49. }
  50. ///<summary>
  51. /// c'tor for the file transaction.
  52. ///</summary>
  53. ///<param name="name">The name of the transaction.</param>
  54. public FileTransaction(string name)
  55. : base(name, TransactionMode.Unspecified, IsolationMode.ReadCommitted)
  56. {
  57. }
  58. #endregion
  59. #region ITransaction Members
  60. // This isn't really relevant with the current architecture
  61. /// <summary>
  62. /// Gets whether the transaction is a distributed transaction.
  63. /// </summary>
  64. public override bool IsAmbient { get; protected set; }
  65. public override bool IsReadOnly
  66. {
  67. get
  68. {
  69. return false;
  70. }
  71. protected set
  72. {
  73. throw new NotImplementedException();
  74. }
  75. }
  76. ///<summary>
  77. /// Gets the name of the transaction.
  78. ///</summary>
  79. public override string Name
  80. {
  81. get { return theName ?? string.Format("FtX #{0}", GetHashCode()); }
  82. }
  83. protected override void InnerBegin()
  84. {
  85. retry:
  86. // we have a ongoing current transaction, join it!
  87. if (System.Transactions.Transaction.Current != null)
  88. {
  89. var ktx = TransactionInterop.GetDtcTransaction(System.Transactions.Transaction.Current)
  90. as IKernelTransaction;
  91. // check for race-condition.
  92. if (ktx == null)
  93. goto retry;
  94. SafeTxHandle handle;
  95. ktx.GetHandle(out handle);
  96. // even though _TransactionHandle can already contain a handle if this thread
  97. // had been yielded just before setting this reference, the "safe"-ness of the wrapper should
  98. // not dispose the other handle which is now removed
  99. _TransactionHandle = handle; // see the link in the remarks to the class for more details about async exceptions
  100. IsAmbient = true;
  101. }
  102. else _TransactionHandle = createTransaction(string.Format("{0} Transaction", theName));
  103. if (!_TransactionHandle.IsInvalid) return;
  104. throw new TransactionException(
  105. "Cannot begin file transaction because we got a null pointer back from CreateTransaction.",
  106. LastEx());
  107. }
  108. private Exception LastEx()
  109. {
  110. return Marshal.GetExceptionForHR(Marshal.GetLastWin32Error());
  111. }
  112. protected override void InnerCommit()
  113. {
  114. if (CommitTransaction(_TransactionHandle)) return;
  115. throw new TransactionException("Commit failed.", LastEx());
  116. }
  117. protected override void InnerRollback()
  118. {
  119. if (!RollbackTransaction(_TransactionHandle))
  120. throw new TransactionException("Rollback failed.",
  121. Marshal.GetExceptionForHR(Marshal.GetLastWin32Error()));
  122. }
  123. #endregion
  124. #region IFileAdapter & IDirectoryAdapter >> Ambiguous members
  125. FileStream IFileAdapter.Create(string path)
  126. {
  127. if (path == null) throw new ArgumentNullException("path");
  128. AssertState(TransactionStatus.Active);
  129. return open(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
  130. }
  131. /// <summary>Creates a directory at the path given.</summary>
  132. ///<param name="path">The path to create the directory at.</param>
  133. bool IDirectoryAdapter.Create(string path)
  134. {
  135. if (path == null) throw new ArgumentNullException("path");
  136. AssertState(TransactionStatus.Active);
  137. path = Path.NormDirSepChars(CleanPathEnd(path));
  138. // we don't need to re-create existing folders.
  139. if (((IDirectoryAdapter)this).Exists(path))
  140. return true;
  141. var nonExistent = new Stack<string>();
  142. nonExistent.Push(path);
  143. var curr = path;
  144. while (!((IDirectoryAdapter)this).Exists(curr)
  145. && (curr.Contains(System.IO.Path.DirectorySeparatorChar)
  146. || curr.Contains(System.IO.Path.AltDirectorySeparatorChar)))
  147. {
  148. curr = Path.GetPathWithoutLastBit(curr);
  149. if (!((IDirectoryAdapter)this).Exists(curr))
  150. nonExistent.Push(curr);
  151. }
  152. while (nonExistent.Count > 0)
  153. {
  154. if (!createDirectoryTransacted(nonExistent.Pop()))
  155. {
  156. var win32Exception = new Win32Exception(Marshal.GetLastWin32Error());
  157. throw new TransactionException(string.Format("Failed to create directory \"{1}\" at path \"{0}\". "
  158. + "See inner exception for more details.", path, curr),
  159. win32Exception);
  160. }
  161. }
  162. return false;
  163. }
  164. /// <summary>
  165. /// Deletes a file as part of a transaction
  166. /// </summary>
  167. /// <param name="filePath"></param>
  168. void IFileAdapter.Delete(string filePath)
  169. {
  170. if (filePath == null) throw new ArgumentNullException("filePath");
  171. AssertState(TransactionStatus.Active);
  172. if (!DeleteFileTransactedW(filePath, _TransactionHandle))
  173. {
  174. var win32Exception = new Win32Exception(Marshal.GetLastWin32Error());
  175. throw new TransactionException("Unable to perform transacted file delete.", win32Exception);
  176. }
  177. }
  178. /// <summary>
  179. /// Deletes a folder recursively.
  180. /// </summary>
  181. /// <param name="path">The directory path to start deleting at!</param>
  182. void IDirectoryAdapter.Delete(string path)
  183. {
  184. if (path == null) throw new ArgumentNullException("path");
  185. AssertState(TransactionStatus.Active);
  186. if (!RemoveDirectoryTransactedW(path, _TransactionHandle))
  187. throw new TransactionException("Unable to delete folder. See inner exception for details.",
  188. new Win32Exception(Marshal.GetLastWin32Error()));
  189. }
  190. bool IFileAdapter.Exists(string filePath)
  191. {
  192. if (filePath == null) throw new ArgumentNullException("filePath");
  193. AssertState(TransactionStatus.Active);
  194. using (var handle = findFirstFileTransacted(filePath, false))
  195. return !handle.IsInvalid;
  196. }
  197. /// <summary>
  198. /// Checks whether the path exists.
  199. /// </summary>
  200. /// <param name="path">Path to check.</param>
  201. /// <returns>True if it exists, false otherwise.</returns>
  202. bool IDirectoryAdapter.Exists(string path)
  203. {
  204. if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path");
  205. AssertState(TransactionStatus.Active);
  206. path = CleanPathEnd(path);
  207. using (var handle = findFirstFileTransacted(path, true))
  208. return !handle.IsInvalid;
  209. }
  210. string IDirectoryAdapter.GetFullPath(string dir)
  211. {
  212. if (dir == null) throw new ArgumentNullException("dir");
  213. AssertState(TransactionStatus.Active);
  214. return getFullPathNameTransacted(dir);
  215. }
  216. string IDirectoryAdapter.MapPath(string path)
  217. {
  218. throw new NotSupportedException("Implemented on the directory adapter.");
  219. }
  220. void IDirectoryAdapter.Move(string originalPath, string newPath)
  221. {
  222. if (originalPath == null) throw new ArgumentNullException("originalPath");
  223. if (newPath == null) throw new ArgumentNullException("newPath");
  224. var da = ((IDirectoryAdapter)this);
  225. if (!da.Exists(originalPath))
  226. throw new DirectoryNotFoundException(string.Format("The path \"{0}\" could not be found. The source directory needs to exist.",
  227. originalPath));
  228. if (!da.Exists(newPath))
  229. da.Create(newPath);
  230. // TODO: Complete.
  231. recurseFiles(originalPath,
  232. f => { Console.WriteLine("file: {0}", f); return true; },
  233. d => { Console.WriteLine("dir: {0}", d); return true; });
  234. }
  235. #endregion
  236. #region IFileAdapter members
  237. /// <summary>
  238. /// Opens a file with RW access.
  239. /// </summary>
  240. /// <param name="filePath"></param>
  241. /// <param name="mode">The file mode, which specifies </param>
  242. /// <returns></returns>
  243. public FileStream Open(string filePath, FileMode mode)
  244. {
  245. if (filePath == null) throw new ArgumentNullException("filePath");
  246. return open(filePath, mode, FileAccess.ReadWrite, FileShare.None);
  247. }
  248. /// <summary>
  249. /// DO NOT USE: Implemented in the file adapter: <see cref="FileAdapter"/>.
  250. /// </summary>
  251. int IFileAdapter.WriteStream(string toFilePath, Stream fromStream)
  252. {
  253. throw new NotSupportedException("Use the file adapter instead!!");
  254. }
  255. ///<summary>
  256. /// Reads all text in a file and returns the string of it.
  257. ///</summary>
  258. ///<param name="path"></param>
  259. ///<param name="encoding"></param>
  260. ///<returns></returns>
  261. public string ReadAllText(string path, Encoding encoding)
  262. {
  263. AssertState(TransactionStatus.Active);
  264. using (var reader = new StreamReader(open(path, FileMode.Open, FileAccess.Read, FileShare.Read), encoding))
  265. {
  266. return reader.ReadToEnd();
  267. }
  268. }
  269. void IFileAdapter.Move(string originalFilePath, string newFilePath)
  270. {
  271. // case 1, the new file path is a folder
  272. if (((IDirectoryAdapter)this).Exists(newFilePath))
  273. {
  274. MoveFileTransacted(originalFilePath, newFilePath.Combine(Path.GetFileName(originalFilePath)), IntPtr.Zero, IntPtr.Zero, MoveFileFlags.CopyAllowed,
  275. _TransactionHandle);
  276. return;
  277. }
  278. // case 2, its not a folder, so assume it's a file.
  279. MoveFileTransacted(originalFilePath, newFilePath, IntPtr.Zero, IntPtr.Zero, MoveFileFlags.CopyAllowed,
  280. _TransactionHandle);
  281. }
  282. /// <summary>
  283. /// Reads all text from a file as part of a transaction
  284. /// </summary>
  285. /// <param name="path"></param>
  286. /// <returns></returns>
  287. public string ReadAllText(string path)
  288. {
  289. AssertState(TransactionStatus.Active);
  290. using (var reader = new StreamReader(open(path, FileMode.Open, FileAccess.Read, FileShare.Read)))
  291. {
  292. return reader.ReadToEnd();
  293. }
  294. }
  295. /// <summary>
  296. /// Writes text to a file as part of a transaction.
  297. /// If the file already contains data, first truncates the file
  298. /// and then writes all contents in the string to the file.
  299. /// </summary>
  300. /// <param name="path">Path to write to</param>
  301. /// <param name="contents">Contents of the file after writing to it.</param>
  302. public void WriteAllText(string path, string contents)
  303. {
  304. AssertState(TransactionStatus.Active);
  305. var exists = ((IFileAdapter) this).Exists(path);
  306. using (var writer = new StreamWriter(open(path, exists ? FileMode.Truncate : FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)))
  307. {
  308. writer.Write(contents);
  309. }
  310. }
  311. #endregion
  312. #region IDirectoryAdapter members
  313. /// <summary>
  314. /// Deletes an empty directory
  315. /// </summary>
  316. /// <param name="path">The path to the folder to delete.</param>
  317. /// <param name="recursively">
  318. /// Whether to delete recursively or not.
  319. /// When recursive, we delete all subfolders and files in the given
  320. /// directory as well.
  321. /// </param>
  322. bool IDirectoryAdapter.Delete(string path, bool recursively)
  323. {
  324. AssertState(TransactionStatus.Active);
  325. return recursively ? deleteRecursive(path)
  326. : RemoveDirectoryTransactedW(path, _TransactionHandle);
  327. }
  328. #endregion
  329. #region Dispose-pattern
  330. ///<summary>
  331. /// Gets whether the transaction is disposed.
  332. ///</summary>
  333. public bool IsDisposed
  334. {
  335. get { return _Disposed; }
  336. }
  337. /// <summary>
  338. /// Allows an <see cref="T:System.Object"/> to attempt to free resources and perform other
  339. /// cleanup operations before the <see cref="T:System.Object"/> is reclaimed by garbage collection.
  340. /// </summary>
  341. ~FileTransaction()
  342. {
  343. Dispose(false);
  344. }
  345. /// <summary>
  346. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  347. /// </summary>
  348. /// <filterpriority>2</filterpriority>
  349. public override void Dispose()
  350. {
  351. Dispose(true);
  352. // the base transaction dispose all resources active, so we must be careful
  353. // and call our own resources first, thereby having to call this afterwards.
  354. base.Dispose();
  355. GC.SuppressFinalize(this);
  356. }
  357. [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
  358. private void Dispose(bool disposing)
  359. {
  360. // no unmanaged code here, just return.
  361. if (!disposing) return;
  362. if (_Disposed) return;
  363. // called via the Dispose() method on IDisposable,
  364. // can use private object references.
  365. if (Status == TransactionStatus.Active)
  366. Rollback();
  367. if (_TransactionHandle != null && !_TransactionHandle.IsInvalid)
  368. _TransactionHandle.Dispose();
  369. _Disposed = true;
  370. }
  371. #endregion
  372. #region C++ Interop
  373. // ReSharper disable InconsistentNaming
  374. // ReSharper disable UnusedMember.Local
  375. // overview here: http://msdn.microsoft.com/en-us/library/aa964885(VS.85).aspx
  376. // helper: http://www.improve.dk/blog/2009/02/14/utilizing-transactional-ntfs-through-dotnet
  377. #region Helper methods
  378. private const int ERROR_TRANSACTIONAL_CONFLICT = 0x1A90;
  379. /// <summary>
  380. /// Creates a file handle with the current ongoing transaction.
  381. /// </summary>
  382. /// <param name="path">The path of the file.</param>
  383. /// <param name="mode">The file mode, i.e. what is going to be done if it exists etc.</param>
  384. /// <param name="access">The access rights this handle has.</param>
  385. /// <param name="share">What other handles may be opened; sharing settings.</param>
  386. /// <returns>A safe file handle. Not null, but may be invalid.</returns>
  387. private FileStream open(string path, FileMode mode, FileAccess access, FileShare share)
  388. {
  389. // Future: Support System.IO.FileOptions which is the dwFlagsAndAttribute parameter.
  390. var fileHandle = CreateFileTransactedW(path,
  391. translateFileAccess(access),
  392. translateFileShare(share),
  393. IntPtr.Zero,
  394. translateFileMode(mode),
  395. 0, IntPtr.Zero,
  396. _TransactionHandle,
  397. IntPtr.Zero, IntPtr.Zero);
  398. if (fileHandle.IsInvalid)
  399. {
  400. var error = Marshal.GetLastWin32Error();
  401. var baseStr = string.Format("Transaction \"{1}\": Unable to open a file descriptor to \"{0}\".", path, Name ?? "[no name]");
  402. if (error == ERROR_TRANSACTIONAL_CONFLICT)
  403. throw new TransactionalConflictException(baseStr
  404. + " You will get this error if you are accessing the transacted file from a non-transacted API before the transaction has "
  405. + "committed. See http://msdn.microsoft.com/en-us/library/aa365536%28VS.85%29.aspx for details.");
  406. throw new TransactionException(baseStr
  407. + "Please see the inner exceptions for details.", new Win32Exception(Marshal.GetLastWin32Error()));
  408. }
  409. return new FileStream(fileHandle, access);
  410. }
  411. /// <summary>
  412. /// Managed -> Native mapping
  413. /// </summary>
  414. /// <param name="mode"></param>
  415. /// <returns></returns>
  416. private static NativeFileMode translateFileMode(FileMode mode)
  417. {
  418. if (mode != FileMode.Append)
  419. return (NativeFileMode)(uint)mode;
  420. return (NativeFileMode)(uint)FileMode.OpenOrCreate;
  421. }
  422. /// <summary>
  423. /// Managed -> Native mapping
  424. /// </summary>
  425. /// <param name="access"></param>
  426. /// <returns></returns>
  427. private static NativeFileAccess translateFileAccess(FileAccess access)
  428. {
  429. switch (access)
  430. {
  431. case FileAccess.Read:
  432. return NativeFileAccess.GenericRead;
  433. case FileAccess.Write:
  434. return NativeFileAccess.GenericWrite;
  435. case FileAccess.ReadWrite:
  436. return NativeFileAccess.GenericRead | NativeFileAccess.GenericWrite;
  437. default:
  438. throw new ArgumentOutOfRangeException("access");
  439. }
  440. }
  441. /// <summary>
  442. /// Direct Managed -> Native mapping
  443. /// </summary>
  444. /// <param name="share"></param>
  445. /// <returns></returns>
  446. private static NativeFileShare translateFileShare(FileShare share)
  447. {
  448. return (NativeFileShare)(uint)share;
  449. }
  450. private bool createDirectoryTransacted(string templatePath,
  451. string dirPath)
  452. {
  453. return CreateDirectoryTransactedW(templatePath,
  454. dirPath,
  455. IntPtr.Zero,
  456. _TransactionHandle);
  457. }
  458. private bool createDirectoryTransacted(string dirPath)
  459. {
  460. return createDirectoryTransacted(null, dirPath);
  461. }
  462. private bool deleteRecursive(string path)
  463. {
  464. if (path == null) throw new ArgumentNullException("path");
  465. if (path == string.Empty) throw new ArgumentException("You can't pass an empty string.");
  466. return recurseFiles(path,
  467. file => DeleteFileTransactedW(file, _TransactionHandle),
  468. dir => RemoveDirectoryTransactedW(dir, _TransactionHandle));
  469. }
  470. private bool recurseFiles(string path,
  471. Func<string, bool> operationOnFiles,
  472. Func<string, bool> operationOnDirectories)
  473. {
  474. if (path == null) throw new ArgumentNullException("path");
  475. if (path == string.Empty) throw new ArgumentException("You can't pass an empty string.");
  476. WIN32_FIND_DATA findData;
  477. bool addPrefix = !path.StartsWith(@"\\?\");
  478. bool ok = true;
  479. string pathWithoutSufflix = addPrefix ? @"\\?\" + Path.GetFullPath(path) : Path.GetFullPath(path);
  480. path = pathWithoutSufflix + "\\*";
  481. using (var findHandle = FindFirstFileTransactedW(path, out findData))
  482. {
  483. if (findHandle.IsInvalid) return false;
  484. do
  485. {
  486. var subPath = pathWithoutSufflix.Combine(findData.cFileName);
  487. if ((findData.dwFileAttributes & (uint)FileAttributes.Directory) != 0)
  488. {
  489. if (findData.cFileName != "." && findData.cFileName != "..")
  490. ok &= deleteRecursive(subPath);
  491. }
  492. else
  493. ok = ok && operationOnFiles(subPath);
  494. }
  495. while (FindNextFile(findHandle, out findData));
  496. }
  497. return ok && operationOnDirectories(pathWithoutSufflix);
  498. }
  499. /*
  500. * Might need to use:
  501. * DWORD WINAPI GetLongPathNameTransacted(
  502. * __in LPCTSTR lpszShortPath,
  503. * __out LPTSTR lpszLongPath,
  504. * __in DWORD cchBuffer,
  505. * __in HANDLE hTransaction
  506. * );
  507. */
  508. private string getFullPathNameTransacted(string dirOrFilePath)
  509. {
  510. var sb = new StringBuilder(512);
  511. retry:
  512. var p = IntPtr.Zero;
  513. int res = GetFullPathNameTransactedW(dirOrFilePath,
  514. sb.Capacity,
  515. sb,
  516. ref p, // here we can check if it's a file or not.
  517. _TransactionHandle);
  518. if (res == 0) // failure
  519. {
  520. throw new TransactionException(string.Format("Could not get full path for \"{0}\", see inner exception for details.",
  521. dirOrFilePath),
  522. Marshal.GetExceptionForHR(Marshal.GetLastWin32Error()));
  523. }
  524. if (res > sb.Capacity)
  525. {
  526. sb.Capacity = res; // update capacity
  527. goto retry; // handle edge case if the path.Length > 512.
  528. }
  529. return sb.ToString();
  530. }
  531. // more examples in C++:
  532. // http://msdn.microsoft.com/en-us/library/aa364963(VS.85).aspx
  533. // http://msdn.microsoft.com/en-us/library/x3txb6xc.aspx
  534. #endregion
  535. #region Native structures, callbacks and enums
  536. [Serializable]
  537. private enum NativeFileMode : uint
  538. {
  539. CREATE_NEW = 1,
  540. CREATE_ALWAYS = 2,
  541. OPEN_EXISTING = 3,
  542. OPEN_ALWAYS = 4,
  543. TRUNCATE_EXISTING = 5
  544. }
  545. [Flags, Serializable]
  546. private enum NativeFileAccess : uint
  547. {
  548. GenericRead = 0x80000000,
  549. GenericWrite = 0x40000000
  550. }
  551. /// <summary>
  552. /// The sharing mode of an object, which can be read, write, both, delete, all of these, or none (refer to the following table).
  553. /// If this parameter is zero and CreateFileTransacted succeeds, the object cannot be shared and cannot be opened again until the handle is closed. For more information, see the Remarks section of this topic.
  554. /// You cannot request a sharing mode that conflicts with the access mode that is specified in an open request that has an open handle, because that would result in the following sharing violation: ERROR_SHARING_VIOLATION. For more information, see Creating and Opening Files.
  555. /// </summary>
  556. [Flags, Serializable]
  557. private enum NativeFileShare : uint
  558. {
  559. /// <summary>
  560. /// Disables subsequent open operations on an object to request any type of access to that object.
  561. /// </summary>
  562. None = 0x00,
  563. /// <summary>
  564. /// Enables subsequent open operations on an object to request read access.
  565. /// Otherwise, other processes cannot open the object if they request read access.
  566. /// If this flag is not specified, but the object has been opened for read access, the function fails.
  567. /// </summary>
  568. Read = 0x01,
  569. /// <summary>
  570. /// Enables subsequent open operations on an object to request write access.
  571. /// Otherwise, other processes cannot open the object if they request write access.
  572. /// If this flag is not specified, but the object has been opened for write access or has a file mapping with write access, the function fails.
  573. /// </summary>
  574. Write = 0x02,
  575. /// <summary>
  576. /// Enables subsequent open operations on an object to request delete access.
  577. /// Otherwise, other processes cannot open the object if they request delete access.
  578. /// If this flag is not specified, but the object has been opened for delete access, the function fails.
  579. /// </summary>
  580. Delete = 0x04
  581. }
  582. enum CopyProgressResult : uint
  583. {
  584. PROGRESS_CONTINUE = 0,
  585. PROGRESS_CANCEL = 1,
  586. PROGRESS_STOP = 2,
  587. PROGRESS_QUIET = 3
  588. }
  589. enum CopyProgressCallbackReason : uint
  590. {
  591. CALLBACK_CHUNK_FINISHED = 0x00000000,
  592. CALLBACK_STREAM_SWITCH = 0x00000001
  593. }
  594. delegate CopyProgressResult CopyProgressRoutine(
  595. long TotalFileSize,
  596. long TotalBytesTransferred,
  597. long StreamSize,
  598. long StreamBytesTransferred,
  599. uint dwStreamNumber,
  600. CopyProgressCallbackReason dwCallbackReason,
  601. SafeFileHandle hSourceFile,
  602. SafeFileHandle hDestinationFile,
  603. IntPtr lpData);
  604. /// <summary>
  605. /// This enumeration states options for moving a file.
  606. /// http://msdn.microsoft.com/en-us/library/aa365241%28VS.85%29.aspx
  607. /// </summary>
  608. [Flags, Serializable]
  609. private enum MoveFileFlags : uint
  610. {
  611. /// <summary>
  612. /// If the file is to be moved to a different volume, the function simulates the move by using the CopyFile and DeleteFile functions.
  613. /// This value cannot be used with MOVEFILE_DELAY_UNTIL_REBOOT.
  614. /// </summary>
  615. CopyAllowed = 0x2,
  616. /// <summary>
  617. /// Reserved for future use.
  618. /// </summary>
  619. CreateHardlink = 0x10,
  620. /// <summary>
  621. /// The system does not move the file until the operating system is restarted. The system moves the file immediately after AUTOCHK is executed, but before creating any paging files. Consequently, this parameter enables the function to delete paging files from previous startups.
  622. /// This value can only be used if the process is in the context of a user who belongs to the administrators group or the LocalSystem account.
  623. /// This value cannot be used with MOVEFILE_COPY_ALLOWED.
  624. /// The write operation to the registry value as detailed in the Remarks section is what is transacted. The file move is finished when the computer restarts, after the transaction is complete.
  625. /// </summary>
  626. DelayUntilReboot = 0x4,
  627. /// <summary>
  628. /// If a file named lpNewFileName exists, the function replaces its contents with the contents of the lpExistingFileName file.
  629. /// This value cannot be used if lpNewFileName or lpExistingFileName names a directory.
  630. /// </summary>
  631. ReplaceExisting = 0x1,
  632. /// <summary>
  633. /// A call to MoveFileTransacted means that the move file operation is complete when the commit operation is completed. This flag is unnecessary; there are no negative affects if this flag is specified, other than an operation slowdown. The function does not return until the file has actually been moved on the disk.
  634. /// Setting this value guarantees that a move performed as a copy and delete operation is flushed to disk before the function returns. The flush occurs at the end of the copy operation.
  635. /// This value has no effect if MOVEFILE_DELAY_UNTIL_REBOOT is set.
  636. /// </summary>
  637. WriteThrough = 0x8
  638. }
  639. #pragma warning disable 1591
  640. ///<summary>
  641. /// Attributes for security interop.
  642. ///</summary>
  643. [StructLayout(LayoutKind.Sequential)]
  644. public struct SECURITY_ATTRIBUTES
  645. {
  646. public int nLength;
  647. public IntPtr lpSecurityDescriptor;
  648. public int bInheritHandle;
  649. }
  650. // The CharSet must match the CharSet of the corresponding PInvoke signature
  651. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
  652. struct WIN32_FIND_DATA
  653. {
  654. public uint dwFileAttributes;
  655. public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
  656. public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
  657. public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
  658. public uint nFileSizeHigh;
  659. public uint nFileSizeLow;
  660. public uint dwReserved0;
  661. public uint dwReserved1;
  662. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
  663. public string cFileName;
  664. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
  665. public string cAlternateFileName;
  666. }
  667. [Serializable]
  668. private enum FINDEX_INFO_LEVELS
  669. {
  670. FindExInfoStandard = 0,
  671. FindExInfoMaxInfoLevel = 1
  672. }
  673. [Serializable]
  674. private enum FINDEX_SEARCH_OPS
  675. {
  676. FindExSearchNameMatch = 0,
  677. FindExSearchLimitToDirectories = 1,
  678. FindExSearchLimitToDevices = 2,
  679. FindExSearchMaxSearchOp = 3
  680. }
  681. #endregion
  682. #region *FileTransacted[W]
  683. /*BOOL WINAPI CreateHardLinkTransacted(
  684. __in LPCTSTR lpFileName,
  685. __in LPCTSTR lpExistingFileName,
  686. __reserved LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  687. __in HANDLE hTransaction
  688. );
  689. */
  690. [return: MarshalAs(UnmanagedType.Bool)]
  691. [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  692. private static extern bool CreateHardLinkTransacted([In] string lpFileName,
  693. [In] string lpExistingFileName,
  694. [In] IntPtr lpSecurityAttributes,
  695. [In] SafeTxHandle hTransaction);
  696. [return: MarshalAs(UnmanagedType.Bool)]
  697. [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  698. private static extern bool MoveFileTransacted([In] string lpExistingFileName,
  699. [In] string lpNewFileName, [In] IntPtr lpProgressRoutine,
  700. [In] IntPtr lpData,
  701. [In] MoveFileFlags dwFlags,
  702. [In] SafeTxHandle hTransaction);
  703. [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
  704. private static extern SafeFileHandle CreateFileTransactedW(
  705. [In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
  706. [In] NativeFileAccess dwDesiredAccess,
  707. [In] NativeFileShare dwShareMode,
  708. [In] IntPtr lpSecurityAttributes,
  709. [In] NativeFileMode dwCreationDisposition,
  710. [In] uint dwFlagsAndAttributes,
  711. [In] IntPtr hTemplateFile,
  712. [In] SafeTxHandle hTransaction,
  713. [In] IntPtr pusMiniVersion,
  714. [In] IntPtr pExtendedParameter);
  715. /// <summary>
  716. /// http://msdn.microsoft.com/en-us/library/aa363916(VS.85).aspx
  717. /// </summary>
  718. [return: MarshalAs(UnmanagedType.Bool)]
  719. [DllImport("kernel32.dll", SetLastError = true)]
  720. private static extern bool DeleteFileTransactedW(
  721. [MarshalAs(UnmanagedType.LPWStr)] string file,
  722. SafeTxHandle transaction);
  723. #endregion
  724. #region *DirectoryTransacted[W]
  725. /// <summary>
  726. /// http://msdn.microsoft.com/en-us/library/aa363857(VS.85).aspx
  727. /// Creates a new directory as a transacted operation, with the attributes of a specified
  728. /// template directory. If the underlying file system supports security on files and
  729. /// directories, the function applies a specified security descriptor to the new directory.
  730. /// The new directory retains the other attributes of the specified template directory.
  731. /// </summary>
  732. /// <param name="lpTemplateDirectory">
  733. /// The path of the directory to use as a template
  734. /// when creating the new directory. This parameter can be NULL.
  735. /// </param>
  736. /// <param name="lpNewDirectory">The path of the directory to be created. </param>
  737. /// <param name="lpSecurityAttributes">A pointer to a SECURITY_ATTRIBUTES structure. The lpSecurityDescriptor member of the structure specifies a security descriptor for the new directory.</param>
  738. /// <param name="hTransaction">A handle to the transaction. This handle is returned by the CreateTransaction function.</param>
  739. /// <returns>True if the call succeeds, otherwise do a GetLastError.</returns>
  740. [DllImport("kernel32.dll", SetLastError = true)]
  741. private static extern bool CreateDirectoryTransactedW(
  742. [MarshalAs(UnmanagedType.LPWStr)] string lpTemplateDirectory,
  743. [MarshalAs(UnmanagedType.LPWStr)] string lpNewDirectory,
  744. IntPtr lpSecurityAttributes,
  745. SafeTxHandle hTransaction);
  746. /// <summary>
  747. /// http://msdn.microsoft.com/en-us/library/aa365490(VS.85).aspx
  748. /// Deletes an existing empty directory as a transacted operation.
  749. /// </summary>
  750. /// <param name="lpPathName">
  751. /// The path of the directory to be removed.
  752. /// The path must specify an empty directory,
  753. /// and the calling process must have delete access to the directory.
  754. /// </param>
  755. /// <param name="hTransaction">A handle to the transaction. This handle is returned by the CreateTransaction function.</param>
  756. /// <returns>True if the call succeeds, otherwise do a GetLastError.</returns>
  757. [DllImport("kernel32.dll", SetLastError = true)]
  758. private static extern bool RemoveDirectoryTransactedW(
  759. [MarshalAs(UnmanagedType.LPWStr)] string lpPathName,
  760. SafeTxHandle hTransaction);
  761. /// <summary>
  762. /// http://msdn.microsoft.com/en-us/library/aa364966(VS.85).aspx
  763. /// Retrieves the full path and file name of the specified file as a transacted operation.
  764. /// </summary>
  765. /// <remarks>
  766. /// GetFullPathNameTransacted merges the name of the current drive and directory
  767. /// with a specified file name to determine the full path and file name of a
  768. /// specified file. It also calculates the address of the file name portion of
  769. /// the full path and file name. This function does not verify that the
  770. /// resulting path and file name are valid, or that they see an existing file
  771. /// on the associated volume.
  772. /// </remarks>
  773. /// <param name="lpFileName">The name of the file. The file must reside on the local computer;
  774. /// otherwise, the function fails and the last error code is set to
  775. /// ERROR_TRANSACTIONS_UNSUPPORTED_REMOTE.</param>
  776. /// <param name="nBufferLength">The size of the buffer to receive the null-terminated string for the drive and path, in TCHARs. </param>
  777. /// <param name="lpBuffer">A pointer to a buffer that receives the null-terminated string for the drive and path.</param>
  778. /// <param name="lpFilePart">A pointer to a buffer that receives the address (in lpBuffer) of the final file name component in the path.
  779. /// Specify NULL if you do not need to receive this information.
  780. /// If lpBuffer points to a directory and not a file, lpFilePart receives 0 (zero).</param>
  781. /// <param name="hTransaction"></param>
  782. /// <returns>If the function succeeds, the return value is the length, in TCHARs, of the string copied to lpBuffer, not including the terminating null character.</returns>
  783. [DllImport( "kernel32.dll", CharSet=CharSet.Auto, SetLastError = true)]
  784. private static extern int GetFullPathNameTransactedW(
  785. [In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
  786. [In] int nBufferLength,
  787. [Out] StringBuilder lpBuffer,
  788. [In, Out] ref IntPtr lpFilePart,
  789. [In] SafeTxHandle hTransaction);
  790. /*
  791. * HANDLE WINAPI FindFirstFileTransacted(
  792. __in LPCTSTR lpFileName,
  793. __in FINDEX_INFO_LEVELS fInfoLevelId,
  794. __out LPVOID lpFindFileData,
  795. __in FINDEX_SEARCH_OPS fSearchOp,
  796. __reserved LPVOID lpSearchFilter,
  797. __in DWORD dwAdditionalFlags,
  798. __in HANDLE hTransaction
  799. );
  800. */
  801. /// <param name="lpFileName"></param>
  802. /// <param name="fInfoLevelId"></param>
  803. /// <param name="lpFindFileData"></param>
  804. /// <param name="fSearchOp">The type of filtering to perform that is different from wildcard matching.</param>
  805. /// <param name="lpSearchFilter">
  806. /// A pointer to the search criteria if the specified fSearchOp needs structured search information.
  807. /// At this time, none of the supported fSearchOp values require extended search information. Therefore, this pointer must be NULL.
  808. /// </param>
  809. /// <param name="dwAdditionalFlags">
  810. /// Specifies additional flags that control the search.
  811. /// FIND_FIRST_EX_CASE_SENSITIVE = 0x1
  812. /// Means: Searches are case-sensitive.
  813. /// </param>
  814. /// <param name="hTransaction"></param>
  815. /// <returns></returns>
  816. [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
  817. private static extern SafeFindHandle FindFirstFileTransactedW(
  818. [In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
  819. [In] FINDEX_INFO_LEVELS fInfoLevelId, // TODO: Won't work.
  820. [Out] out WIN32_FIND_DATA lpFindFileData,
  821. [In] FINDEX_SEARCH_OPS fSearchOp,
  822. IntPtr lpSearchFilter,
  823. [In] uint dwAdditionalFlags,
  824. [In] SafeTxHandle hTransaction);
  825. private SafeFindHandle findFirstFileTransacted(string filePath, bool directory)
  826. {
  827. WIN32_FIND_DATA data;
  828. #if MONO
  829. uint caseSensitive = 0x1;
  830. #else
  831. uint caseSensitive = 0;
  832. #endif
  833. return FindFirstFileTransactedW(filePath,
  834. FINDEX_INFO_LEVELS.FindExInfoStandard, out data,
  835. directory
  836. ? FINDEX_SEARCH_OPS.FindExSearchLimitToDirectories
  837. : FINDEX_SEARCH_OPS.FindExSearchNameMatch,
  838. IntPtr.Zero, caseSensitive, _TransactionHandle);
  839. }
  840. private SafeFindHandle FindFirstFileTransactedW(string lpFileName,
  841. out WIN32_FIND_DATA lpFindFileData)
  842. {
  843. return FindFirstFileTransactedW(lpFileName, FINDEX_INFO_LEVELS.FindExInfoStandard,
  844. out lpFindFileData,
  845. FINDEX_SEARCH_OPS.FindExSearchNameMatch,
  846. IntPtr.Zero, 0,
  847. _TransactionHandle);
  848. }
  849. // not transacted
  850. [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
  851. private static extern bool FindNextFile(SafeFindHandle hFindFile,
  852. out WIN32_FIND_DATA lpFindFileData);
  853. #endregion
  854. #region Kernel transaction manager
  855. /// <summary>
  856. /// Creates a new transaction object. Passing too long a description will cause problems. This behaviour is indeterminate right now.
  857. /// </summary>
  858. /// <remarks>
  859. /// Don't pass unicode to the description (there's no Wide-version of this function
  860. /// in the kernel).
  861. /// http://msdn.microsoft.com/en-us/library/aa366011%28VS.85%29.aspx
  862. /// </remarks>
  863. /// <param name="lpTransactionAttributes">
  864. /// A pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle
  865. /// can be inherited by child processes. If this parameter is NULL, the handle cannot be inherited.
  866. /// The lpSecurityDescriptor member of the structure specifies a security descriptor for
  867. /// the new event. If lpTransactionAttributes is NULL, the object gets a default
  868. /// security descriptor. The access control lists (ACL) in the default security
  869. /// descriptor for a transaction come from the primary or impersonation token of the creator.
  870. /// </param>
  871. /// <param name="uow">Reserved. Must be zero (0).</param>
  872. /// <param name="createOptions">
  873. /// Any optional transaction instructions.
  874. /// Value: TRANSACTION_DO_NOT_PROMOTE
  875. /// Meaning: The transaction cannot be distributed.
  876. /// </param>
  877. /// <param name="isolationLevel">Reserved; specify zero (0).</param>
  878. /// <param name="isolationFlags">Reserved; specify zero (0).</param>
  879. /// <param name="timeout">
  880. /// The time, in milliseconds, when the transaction will be aborted if it has not already
  881. /// reached the prepared state.
  882. /// Specify NULL to provide an infinite timeout.
  883. /// </param>
  884. /// <param name="description">A user-readable description of the transaction.</param>
  885. /// <returns>
  886. /// If the function succeeds, the return value is a handle to the transaction.
  887. /// If the function fails, the return value is INVALID_HANDLE_VALUE.
  888. /// </returns>
  889. [DllImport("ktmw32.dll", SetLastError = true)]
  890. private static extern IntPtr CreateTransaction(
  891. IntPtr lpTransactionAttributes,
  892. IntPtr uow,
  893. uint createOptions,
  894. uint isolationLevel,
  895. uint isolationFlags,
  896. uint timeout,
  897. string description);
  898. private static SafeTxHandle createTransaction(string description)
  899. {
  900. return new SafeTxHandle(CreateTransaction(IntPtr.Zero, IntPtr.Zero, 0, 0, 0, 0, description));
  901. }
  902. /// <summary>
  903. /// Requests that the specified transaction be committed.
  904. /// </summary>
  905. /// <remarks>You can commit any transaction handle that has been opened
  906. /// or created using the TRANSACTION_COMMIT permission; any application can
  907. /// commit a transaction, not just the creator.
  908. /// This function can only be called if the transaction is still active,
  909. /// not prepared, pre-prepared, or rolled back.</remarks>
  910. /// <param name="transaction">
  911. /// This handle must have been opened with the TRANSACTION_COMMIT access right.
  912. /// For more information, see KTM Security and Access Rights.</param>
  913. /// <returns></returns>
  914. [DllImport("ktmw32.dll", SetLastError = true)]
  915. private static extern bool CommitTransaction(SafeTxHandle transaction);
  916. /// <summary>
  917. /// Requests that the specified transaction be rolled back. This function is synchronous.
  918. /// </summary>
  919. /// <param name="transaction">A handle to the transaction.</param>
  920. /// <returns>If the function succeeds, the return value is nonzero.</returns>
  921. [DllImport("ktmw32.dll", SetLastError = true)]
  922. private static extern bool RollbackTransaction(SafeTxHandle transaction);
  923. #endregion
  924. // ReSharper restore UnusedMember.Local
  925. // ReSharper restore InconsistentNaming
  926. #pragma warning restore 1591
  927. #endregion
  928. #region Minimal utils
  929. private static string CleanPathEnd(string path)
  930. {
  931. return path.TrimEnd('/', '\\');
  932. }
  933. #endregion
  934. }
  935. }