IdTransactedFileStream.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. unit IdTransactedFileStream;
  2. interface
  3. {$I IdCompilerDefines.inc}
  4. {
  5. Original author: Grahame Grieve
  6. His notes are:
  7. If you want transactional file handling, with commit and rollback,
  8. then this is the unit for you. It provides transactional safety on
  9. Vista, win7, and windows server 2008, and just falls through to
  10. normal file handling on earlier versions of windows.
  11. There's a couple of issues with the wrapper classes TKernelTransaction
  12. and TIdTransactedFileStream:
  13. - you can specify how you want file reading to work with a transactional
  14. isolation. I don't surface this.
  15. - you can elevate the transactions to coordinate with DTC. They don't
  16. do this (for instance, you could put your file handling in the same
  17. transaction as an SQLServer transaction). I haven't done this - but if
  18. you do this, I'd love a copy ;-)
  19. you use it like this:
  20. procedure StringToFile(const AStr, AFilename: String);
  21. var
  22. oFileStream: TIdTransactedFileStream;
  23. oTransaction : TKernelTransaction;
  24. begin
  25. oTransaction := TIdKernelTransaction.Create('Save Content to file '+aFilename, false);
  26. Try
  27. Try
  28. oFileStream := TIdTransactedFileStream.Create(AFilename, fmCreate);
  29. try
  30. if AStr <> '' then
  31. WriteStringToStream(LFileStream, AStr, IndyTextEncoding_8Bit);
  32. finally
  33. LFileStream.Free;
  34. end;
  35. oTransaction.Commit;
  36. Except
  37. oTransaction.Rollback;
  38. raise;
  39. End;
  40. Finally
  41. oTransaction.Free;
  42. End;
  43. end;
  44. anyway - maybe useful in temporary file handling with file and email? I've
  45. been burnt with temporary files and server crashes before.
  46. }
  47. uses
  48. {$IF DEFINED(WIN32) OR DEFINED(WIN64)}
  49. Windows,
  50. Consts,
  51. {$IFEND}
  52. Classes, SysUtils, IdGlobal;
  53. {$IF DEFINED(WIN32) OR DEFINED(WIN64)}
  54. const
  55. TRANSACTION_DO_NOT_PROMOTE = 1;
  56. TXFS_MINIVERSION_COMMITTED_VIEW : Word = $0000; // The view of the file as of its last commit.
  57. TXFS_MINIVERSION_DIRTY_VIEW : Word = $FFFE; // The view of the file as it is being modified by the transaction.
  58. TXFS_MINIVERSION_DEFAULT_VIEW : Word = $FFFF; // Either the committed or dirty view of the file, depending on the context.
  59. // A transaction that is modifying the file gets the dirty view, while a transaction
  60. // that is not modifying the file gets the committed view.
  61. // remember to close the transaction handle. Use the CloseTransaction function here to avoid problems if the transactions are not available
  62. type
  63. TktmCreateTransaction = function (lpSecurityAttributes: PSecurityAttributes;
  64. pUow : Pointer;
  65. CreateOptions, IsolationLevel, IsolationFlags, Timeout : DWORD;
  66. Description : PWideChar) : THandle; stdcall;
  67. TktmCreateFileTransacted = function (lpFileName: PChar;
  68. dwDesiredAccess, dwShareMode: DWORD;
  69. lpSecurityAttributes: PSecurityAttributes;
  70. dwCreationDisposition, dwFlagsAndAttributes: DWORD;
  71. hTemplateFile: THandle;
  72. hTransaction : THandle;
  73. MiniVersion : Word;
  74. pExtendedParameter : Pointer): THandle; stdcall;
  75. TktmCommitTransaction = function (hTransaction : THandle) : Boolean; stdcall;
  76. TktmRollbackTransaction = function (hTransaction : THandle) :
  77. Boolean; stdcall;
  78. TktmCloseTransaction = function (hTransaction : THandle) : Boolean; stdcall;
  79. var
  80. CreateTransaction : TktmCreateTransaction;
  81. CreateFileTransacted : TktmCreateFileTransacted;
  82. CommitTransaction : TktmCommitTransaction;
  83. RollbackTransaction : TktmRollbackTransaction;
  84. CloseTransaction : TktmCloseTransaction;
  85. {$IFEND}
  86. function IsTransactionsWorking : Boolean;
  87. type
  88. TIdKernelTransaction = class (TObject)
  89. protected
  90. FHandle : THandle;
  91. public
  92. constructor Create(Const sDescription : String; bCanPromote : Boolean = false);
  93. destructor Destroy; override;
  94. function IsTransactional : Boolean;
  95. procedure Commit;
  96. procedure RollBack;
  97. end;
  98. TIdTransactedFileStream = class(THandleStream)
  99. {$IF NOT (DEFINED(WIN32) OR DEFINED(WIN64))}
  100. protected
  101. FFileStream : TFileStream;
  102. {$IFEND}
  103. public
  104. constructor Create(const FileName: string; Mode: Word; oTransaction : TIdKernelTransaction);
  105. destructor Destroy; override;
  106. end;
  107. implementation
  108. uses
  109. RTLConsts;
  110. {$IF DEFINED(WIN32) OR DEFINED(WIN64)}
  111. var
  112. GHandleKtm : HModule;
  113. GHandleKernel : HModule;
  114. function DummyCreateTransaction(lpSecurityAttributes: PSecurityAttributes;
  115. pUow : Pointer; CreateOptions, IsolationLevel,
  116. IsolationFlags, Timeout : DWORD;
  117. Description : PWideChar) : THandle; stdcall;
  118. begin
  119. Result := 1;
  120. end;
  121. function DummyCreateFileTransacted(lpFileName: PChar;
  122. dwDesiredAccess, dwShareMode: DWORD;
  123. lpSecurityAttributes: PSecurityAttributes;
  124. dwCreationDisposition, dwFlagsAndAttributes: DWORD;
  125. hTemplateFile: THandle;
  126. hTransaction : THandle;
  127. MiniVersion : Word;
  128. pExtendedParameter : Pointer): THandle; stdcall;
  129. begin
  130. Result := CreateFile(lpFilename, dwDesiredAccess, dwShareMode,
  131. lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes,
  132. hTemplateFile);
  133. end;
  134. function DummyCommitTransaction(hTransaction : THandle) : Boolean; stdcall;
  135. begin
  136. assert(hTransaction = 1);
  137. Result := True;
  138. end;
  139. function DummyRollbackTransaction(hTransaction : THandle) : Boolean; stdcall;
  140. begin
  141. assert(hTransaction = 1);
  142. Result := True;
  143. end;
  144. function DummyCloseTransaction(hTransaction : THandle) : Boolean; stdcall;
  145. begin
  146. assert(hTransaction = 1);
  147. Result := True;
  148. end;
  149. procedure LoadDll;
  150. begin
  151. GHandleKtm := LoadLibrary('ktmw32.dll');
  152. if GHandleKtm <> 0 Then begin
  153. GHandleKernel := GetModuleHandle('Kernel32.dll'); //LoadLibrary('kernel32.dll');
  154. @CreateTransaction := LoadLibFunction(GHandleKtm, 'CreateTransaction');
  155. @CommitTransaction := LoadLibFunction(GHandleKtm, 'CommitTransaction');
  156. @RollbackTransaction := LoadLibFunction(GHandleKtm, 'RollbackTransaction');
  157. @CloseTransaction := LoadLibFunction(GHandleKernel, 'CloseHandle');
  158. @CreateFileTransacted := LoadLibFunction(GHandleKernel, {$IFDEF UNICODE}'CreateFileTransactedW'{$ELSE}'CreateFileTransactedA'{$ENDIF});
  159. end else begin
  160. @CreateTransaction := @DummyCreateTransaction;
  161. @CommitTransaction := @DummyCommitTransaction;
  162. @RollbackTransaction := @DummyRollbackTransaction;
  163. @CloseTransaction := @DummyCloseTransaction;
  164. @CreateFileTransacted := @DummyCreateFileTransacted;
  165. end;
  166. end;
  167. procedure UnloadDll;
  168. begin
  169. if GHandleKtm <> 0 then begin
  170. FreeLibrary(GHandleKtm);
  171. GHandleKtm := 0;
  172. //FreeLibrary(GHandleKernel);
  173. //GHandleKernel := 0;
  174. end
  175. end;
  176. function IsTransactionsWorking : Boolean;
  177. {$IFDEF USE_INLINE} inline; {$ENDIF}
  178. begin
  179. Result := GHandleKtm <> 0;
  180. end;
  181. {$ELSE}
  182. function IsTransactionsWorking : Boolean;
  183. {$IFDEF USE_INLINE} inline; {$ENDIF}
  184. begin
  185. Result := False;
  186. end;
  187. {$IFEND}
  188. { TIdKernelTransaction }
  189. constructor TIdKernelTransaction.Create(const sDescription: String; bCanPromote : Boolean);
  190. {$IF DEFINED(WIN32) OR DEFINED(WIN64)}
  191. var
  192. Opts: DWORD;
  193. {$IFEND}
  194. begin
  195. inherited Create;
  196. {$IF DEFINED(WIN32) OR DEFINED(WIN64)}
  197. if bCanPromote then begin
  198. Opts := 0;
  199. end else begin
  200. Opts := TRANSACTION_DO_NOT_PROMOTE;
  201. end;
  202. FHandle := CreateTransaction(nil, nil, Opts, 0, 0, 0,
  203. {$IFDEF STRING_UNICODE_MISMATCH}
  204. PIdPlatformChar(TIdPlatformString(sDescription))
  205. {$ELSE}
  206. PChar(sDescription)
  207. {$ENDIF}
  208. );
  209. {$IFEND}
  210. end;
  211. destructor TIdKernelTransaction.Destroy;
  212. begin
  213. {$IF DEFINED(WIN32) OR DEFINED(WIN64)}
  214. if FHandle <> INVALID_HANDLE_VALUE then begin
  215. CloseTransaction(FHandle);
  216. end;
  217. {$IFEND}
  218. inherited Destroy;
  219. end;
  220. procedure TIdKernelTransaction.Commit;
  221. begin
  222. {$IF DEFINED(WIN32) OR DEFINED(WIN64)}
  223. if not CommitTransaction(FHandle) then begin
  224. IndyRaiseLastError;
  225. end;
  226. {$IFEND}
  227. end;
  228. function TIdKernelTransaction.IsTransactional: Boolean;
  229. begin
  230. Result := IsTransactionsWorking;
  231. end;
  232. procedure TIdKernelTransaction.RollBack;
  233. begin
  234. {$IF DEFINED(WIN32) OR DEFINED(WIN64)}
  235. if not RollbackTransaction(FHandle) then begin
  236. IndyRaiseLastError;
  237. end;
  238. {$IFEND}
  239. end;
  240. {$IF DEFINED(WIN32) OR DEFINED(WIN64)}
  241. function FileCreateTransacted(const FileName: string; hTransaction : THandle): THandle;
  242. begin
  243. Result := THandle(CreateFileTransacted(
  244. {$IFDEF STRING_UNICODE_MISMATCH}
  245. PIdPlatformChar(TIdPlatformString(FileName))
  246. {$ELSE}PChar(FileName){$ENDIF},
  247. GENERIC_READ or GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0, hTransaction, 0, nil));
  248. if Result = INVALID_HANDLE_VALUE Then begin
  249. IndyRaiseLastError;
  250. end;
  251. end;
  252. function FileOpenTransacted(const FileName: string; Mode: LongWord; hTransaction: THandle): THandle;
  253. const
  254. AccessMode: array[0..2] of LongWord = (
  255. GENERIC_READ,
  256. GENERIC_WRITE,
  257. GENERIC_READ or GENERIC_WRITE);
  258. ShareMode: array[0..4] of LongWord = (
  259. 0,
  260. 0,
  261. FILE_SHARE_READ,
  262. FILE_SHARE_WRITE,
  263. FILE_SHARE_READ or FILE_SHARE_WRITE);
  264. begin
  265. Result := THandle(CreateFileTransacted(
  266. {$IFDEF STRING_UNICODE_MISMATCH}
  267. PIdPlatformChar(TIdPlatformString(FileName))
  268. {$ELSE}
  269. PChar(FileName)
  270. {$ENDIF},
  271. AccessMode[Mode and 3], ShareMode[(Mode and $F0) shr 4], nil, OPEN_EXISTING,
  272. FILE_ATTRIBUTE_NORMAL, 0, hTransaction, TXFS_MINIVERSION_DEFAULT_VIEW, nil));
  273. end;
  274. {$IFEND}
  275. { TIdTransactedFileStream }
  276. {$IF DEFINED(WIN32) OR DEFINED(WIN64)}
  277. constructor TIdTransactedFileStream.Create(const FileName: string; Mode: Word; oTransaction: TIdKernelTransaction);
  278. var
  279. LHandle : THandle;
  280. begin
  281. if Mode = fmCreate then begin
  282. LHandle := FileCreateTransacted(FileName, oTransaction.FHandle);
  283. if LHandle = INVALID_HANDLE_VALUE then begin
  284. raise EFCreateError.CreateResFmt(@SFCreateError, [FileName]);
  285. end;
  286. end else begin
  287. LHandle := FileOpenTransacted(FileName, Mode, oTransaction.FHandle);
  288. if LHandle = INVALID_HANDLE_VALUE then begin
  289. raise EFOpenError.CreateResFmt(@SFOpenError, [FileName]);
  290. end;
  291. end;
  292. inherited Create(LHandle);
  293. end;
  294. {$ELSE}
  295. constructor TIdTransactedFileStream.Create(const FileName: string; Mode: Word; oTransaction: TIdKernelTransaction);
  296. begin
  297. FFileStream := FFileStream.Create(FileName, Mode);
  298. inherited Create(LStream.Handle);
  299. end;
  300. {$IFEND}
  301. destructor TIdTransactedFileStream.Destroy;
  302. begin
  303. {$IF DEFINED(WIN32) OR DEFINED(WIN64)}
  304. if Handle <> INVALID_HANDLE_VALUE then begin
  305. FileClose(Handle);
  306. end;
  307. {$ELSE}
  308. //we have to dereference our copy of the THandle so we don't free it twice.
  309. FHandle := INVALID_HANDLE_VALUE;
  310. FFileStream.Free;
  311. {$IFEND}
  312. inherited Destroy;
  313. end;
  314. {$IF DEFINED(WIN32) OR DEFINED(WIN64)}
  315. initialization
  316. LoadDLL;
  317. finalization
  318. UnloadDLL;
  319. {$IFEND}
  320. end.