IdTransactedFileStream.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  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 Length(AStr) > 0 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. {$IFDEF WIN32_OR_WIN64}
  49. Windows,
  50. Consts,
  51. {$ENDIF}
  52. Classes, SysUtils, IdGlobal;
  53. {$IFDEF WIN32_OR_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. {$ENDIF}
  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. {$IFNDEF WIN32_OR_WIN64}
  100. protected
  101. FFileStream : TFileStream;
  102. {$ENDIF}
  103. public
  104. constructor Create(const FileName: string; Mode: Word; oTransaction : TIdKernelTransaction);
  105. destructor Destroy; override;
  106. end;
  107. implementation
  108. uses RTLConsts;
  109. {$IFDEF WIN32_OR_WIN64}
  110. var
  111. GHandleKtm : HModule;
  112. GHandleKernel : HModule;
  113. function DummyCreateTransaction(lpSecurityAttributes: PSecurityAttributes;
  114. pUow : Pointer; CreateOptions, IsolationLevel,
  115. IsolationFlags, Timeout : DWORD;
  116. Description : PWideChar) : THandle; stdcall;
  117. begin
  118. result := 1;
  119. end;
  120. function DummyCreateFileTransacted(lpFileName: PChar;
  121. dwDesiredAccess, dwShareMode: DWORD;
  122. lpSecurityAttributes: PSecurityAttributes;
  123. dwCreationDisposition, dwFlagsAndAttributes: DWORD;
  124. hTemplateFile: THandle;
  125. hTransaction : THandle;
  126. MiniVersion : Word;
  127. pExtendedParameter : Pointer): THandle; stdcall;
  128. begin
  129. result := CreateFile(lpFilename, dwDesiredAccess, dwShareMode,
  130. lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes,
  131. hTemplateFile);
  132. end;
  133. function DummyCommitTransaction(hTransaction : THandle) : Boolean; stdcall;
  134. begin
  135. assert(hTransaction = 1);
  136. result := true;
  137. end;
  138. function DummyRollbackTransaction(hTransaction : THandle) : Boolean; stdcall;
  139. begin
  140. assert(hTransaction = 1);
  141. result := true;
  142. end;
  143. function DummyCloseTransaction(hTransaction : THandle) : Boolean; stdcall;
  144. begin
  145. assert(hTransaction = 1);
  146. result := true;
  147. end;
  148. procedure LoadDll;
  149. begin
  150. GHandleKtm := LoadLibrary('ktmw32.dll');
  151. if GHandleKtm <> 0 Then begin
  152. GHandleKernel := GetModuleHandle('Kernel32.dll'); //LoadLibrary('kernel32.dll');
  153. @CreateTransaction := LoadLibFunction(GHandleKtm, 'CreateTransaction');
  154. @CommitTransaction := LoadLibFunction(GHandleKtm, 'CommitTransaction');
  155. @RollbackTransaction := LoadLibFunction(GHandleKtm, 'RollbackTransaction');
  156. @CloseTransaction := LoadLibFunction(GHandleKernel, 'CloseHandle');
  157. {$IFDEF UNICODE}
  158. @CreateFileTransacted := LoadLibFunction(GHandleKernel, 'CreateFileTransactedW');
  159. {$ELSE}
  160. @CreateFileTransacted := LoadLibFunction(GHandleKernel, 'CreateFileTransactedA');
  161. {$ENDIF}
  162. end else begin
  163. @CreateTransaction := @DummyCreateTransaction;
  164. @CommitTransaction := @DummyCommitTransaction;
  165. @RollbackTransaction := @DummyRollbackTransaction;
  166. @CloseTransaction := @DummyCloseTransaction;
  167. @CreateFileTransacted := @DummyCreateFileTransacted;
  168. end;
  169. end;
  170. procedure UnloadDll;
  171. begin
  172. if GHandleKtm <> 0 then begin
  173. freelibrary(GHandleKtm);
  174. // freelibrary(GHandleKernel);
  175. end
  176. end;
  177. function IsTransactionsWorking : Boolean;
  178. {$IFDEF USE_INLINE} inline; {$ENDIF}
  179. begin
  180. result := GHandleKtm <> 0;
  181. end;
  182. {$ELSE}
  183. function IsTransactionsWorking : Boolean;
  184. {$IFDEF USE_INLINE} inline; {$ENDIF}
  185. begin
  186. result := False;
  187. end;
  188. {$ENDIF}
  189. { TIdKernelTransaction }
  190. constructor TIdKernelTransaction.Create(const sDescription: String; bCanPromote : Boolean);
  191. var
  192. pDesc : PWideChar;
  193. begin
  194. inherited Create;
  195. {$IFDEF UNICODE}
  196. GetMem(pDesc, length(sDescription) + 2);
  197. try
  198. StringToWideChar(sDescription, pDesc, length(sDescription) + 2);
  199. {$ELSE}
  200. GetMem(pDesc, length(sDescription) * 2 + 4);
  201. try
  202. StringToWideChar(sDescription, pDesc, length(sDescription) * 2 + 4);
  203. {$ENDIF}
  204. {$IFDEF WIN32_OR_WIN64}
  205. if bCanPromote Then begin
  206. FHandle := CreateTransaction(nil, nil, 0, 0, 0, 0, pDesc);
  207. end else begin
  208. FHandle := CreateTransaction(nil, nil, TRANSACTION_DO_NOT_PROMOTE, 0, 0, 0, pDesc);
  209. end;
  210. {$ENDIF}
  211. finally
  212. FreeMem(pDesc);
  213. end;
  214. end;
  215. destructor TIdKernelTransaction.Destroy;
  216. begin
  217. {$IFDEF WIN32_OR_WIN64}
  218. CloseTransaction(FHandle);
  219. {$ENDIF}
  220. inherited Destroy;
  221. end;
  222. procedure TIdKernelTransaction.Commit;
  223. begin
  224. {$IFDEF WIN32_OR_WIN64}
  225. if not CommitTransaction(FHandle) then begin
  226. IndyRaiseLastError;
  227. end;
  228. {$ENDIF}
  229. end;
  230. function TIdKernelTransaction.IsTransactional: Boolean;
  231. begin
  232. result := IsTransactionsWorking;
  233. end;
  234. procedure TIdKernelTransaction.RollBack;
  235. begin
  236. {$IFDEF WIN32_OR_WIN64}
  237. if not RollbackTransaction(FHandle) then begin
  238. IndyRaiseLastError;
  239. end;
  240. {$ENDIF}
  241. end;
  242. {$IFDEF WIN32_OR_WIN64}
  243. function FileCreateTransacted(const FileName: string; hTransaction : THandle): THandle;
  244. begin
  245. Result := THandle(CreateFileTransacted(PChar(FileName), GENERIC_READ or GENERIC_WRITE,
  246. 0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0, hTransaction, 0, nil));
  247. if result = INVALID_HANDLE_VALUE Then begin
  248. IndyRaiseLastError;
  249. end;
  250. end;
  251. function FileOpenTransacted(const FileName: string; Mode: LongWord; hTransaction : THandle): THandle;
  252. const
  253. AccessMode: array[0..2] of LongWord = (
  254. GENERIC_READ,
  255. GENERIC_WRITE,
  256. GENERIC_READ or GENERIC_WRITE);
  257. ShareMode: array[0..4] of LongWord = (
  258. 0,
  259. 0,
  260. FILE_SHARE_READ,
  261. FILE_SHARE_WRITE,
  262. FILE_SHARE_READ or FILE_SHARE_WRITE);
  263. begin
  264. Result := THandle(CreateFileTransacted(PChar(FileName),AccessMode[Mode and 3],
  265. ShareMode[(Mode and $F0) shr 4], nil, OPEN_EXISTING,
  266. FILE_ATTRIBUTE_NORMAL, 0, hTransaction,TXFS_MINIVERSION_DEFAULT_VIEW, nil));
  267. end;
  268. {$ENDIF}
  269. { TIdTransactedFileStream }
  270. {$IFDEF WIN32_OR_WIN64}
  271. constructor TIdTransactedFileStream.Create(const FileName: string; Mode: Word; oTransaction: TIdKernelTransaction);
  272. var
  273. aHandle : THandle;
  274. begin
  275. if Mode = fmCreate then begin
  276. aHandle := FileCreateTransacted(FileName, oTransaction.FHandle);
  277. if aHandle = INVALID_HANDLE_VALUE then begin
  278. raise EFCreateError.CreateResFmt(@SFCreateError, [FileName]);
  279. end;
  280. end else begin
  281. aHandle := FileOpenTransacted(FileName, Mode, oTransaction.FHandle);
  282. if aHandle = INVALID_HANDLE_VALUE then begin
  283. raise EFOpenError.CreateResFmt(@SFOpenError, [FileName]);
  284. end;
  285. end;
  286. inherited Create(ahandle);
  287. end;
  288. {$ELSE}
  289. constructor TIdTransactedFileStream.Create(const FileName: string; Mode: Word; oTransaction: TIdKernelTransaction);
  290. var LStream : TFileStream;
  291. begin
  292. LStream := FFileStream.Create(FileName,Mode);
  293. inherited Create ( LStream.Handle);
  294. FFileStream := LStream;
  295. end;
  296. {$ENDIF}
  297. destructor TIdTransactedFileStream.Destroy ;
  298. begin
  299. {$IFDEF WIN32_OR_WIN64}
  300. if Handle = INVALID_HANDLE_VALUE then begin
  301. FileClose(Handle);
  302. end;
  303. inherited Destroy;
  304. {$ELSE}
  305. //we have to deference our copy of the THandle so we don't free it twice.
  306. FHandle := INVALID_HANDLE_VALUE;
  307. FreeAndNil( FFileStream );
  308. inherited Destroy;
  309. {$ENDIF}
  310. end;
  311. {$IFDEF WIN32_OR_WIN64}
  312. initialization
  313. LoadDLL;
  314. finalization
  315. UnloadDLL;
  316. {$ENDIF}
  317. End.