closablefilestream.pas 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. {
  2. FPCRes - Free Pascal Resource Converter
  3. Part of the Free Pascal distribution
  4. Copyright (C) 2008-2010 by Giulio Bernardi
  5. Support for file streams that can be temporarily closed
  6. See the file COPYING, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. }
  12. unit closablefilestream;
  13. {$MODE OBJFPC} {$H+}
  14. interface
  15. uses
  16. Classes, SysUtils;
  17. type
  18. TClosableFileNotifier = class;
  19. { TClosableFileStream }
  20. TClosableFileStream = class(TStream)
  21. private
  22. fStream : TFileStream;
  23. fFileName : string;
  24. fMode : word;
  25. fListener : TClosableFileNotifier;
  26. fPosition : int64;
  27. procedure EnsureHandleOpen;
  28. protected
  29. procedure SetSize(const NewSize: Int64); override;
  30. function RetryOpen : boolean;
  31. public
  32. constructor Create(const AFileName: String; Mode: Word);
  33. destructor Destroy; override;
  34. function Read(var Buffer; Count: Longint): Longint; override;
  35. function Write(const Buffer; Count: Longint): Longint; override;
  36. function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
  37. procedure CloseHandle;
  38. end;
  39. TClosableFileNotifier = class
  40. protected
  41. procedure NotifyFileOpened(aStream : TClosableFileStream); virtual; abstract;
  42. function NotifyOpenFailed(aStream: TClosableFileStream) : boolean;
  43. virtual; abstract;
  44. end;
  45. implementation
  46. type
  47. PQueueItem = ^TQueueItem;
  48. TQueueItem = record
  49. prev : PQueueItem;
  50. next : PQueueItem;
  51. data : pointer;
  52. end;
  53. { TFIFOQueue }
  54. TFIFOQueue = class
  55. private
  56. fHead : PQueueItem;
  57. fTail : PQueueItem;
  58. fCount : integer;
  59. protected
  60. public
  61. constructor Create;
  62. destructor Destroy; override;
  63. procedure Add(p : pointer);
  64. function Remove : Pointer;
  65. property Count : integer read fCount;
  66. end;
  67. { TFileKeeper }
  68. TFileKeeper = class(TClosableFileNotifier)
  69. private
  70. fOpenFiles : TFIFOQueue;
  71. fMaxOpen : longint;
  72. private
  73. procedure CloseFiles(const count : integer);
  74. protected
  75. procedure NotifyFileOpened(aStream : TClosableFileStream); override;
  76. function NotifyOpenFailed(aStream: TClosableFileStream) : boolean; override;
  77. public
  78. constructor Create;
  79. destructor Destroy; override;
  80. end;
  81. { TFIFOQueue }
  82. constructor TFIFOQueue.Create;
  83. begin
  84. fHead:=nil;
  85. fTail:=nil;
  86. fCount:=0;
  87. end;
  88. destructor TFIFOQueue.Destroy;
  89. var
  90. el : PQueueItem;
  91. begin
  92. while fHead<>nil do
  93. begin
  94. el:=fHead;
  95. fHead:=fHead^.next;
  96. FreeMem(el);
  97. end;
  98. end;
  99. procedure TFIFOQueue.Add(p: pointer);
  100. var
  101. el : PQueueItem;
  102. begin
  103. el:=GetMem(sizeof(TQueueItem));
  104. el^.data:=p;
  105. el^.prev:=nil;
  106. el^.next:=fHead;
  107. if fHead<>nil then
  108. fHead^.prev:=el;
  109. fHead:=el;
  110. if fTail=nil then
  111. fTail:=fHead;
  112. inc(fCount);
  113. end;
  114. function TFIFOQueue.Remove: Pointer;
  115. var
  116. el : PQueueItem;
  117. begin
  118. if fCount=0 then
  119. begin
  120. Result:=nil;
  121. exit;
  122. end;
  123. Result:=fTail^.data;
  124. el:=fTail;
  125. fTail:=fTail^.prev;
  126. if fTail=nil then
  127. fHead:=nil
  128. else
  129. fTail^.next:=nil;
  130. FreeMem(el);
  131. dec(fCount);
  132. end;
  133. { TFileKeeper }
  134. procedure TFileKeeper.CloseFiles(const count: integer);
  135. var
  136. i,max : integer;
  137. tmp : TClosableFileStream;
  138. begin
  139. max:=count;
  140. if fOpenFiles.Count<max then
  141. max:=fOpenFiles.Count;
  142. for i:=0 to max-1 do
  143. begin
  144. tmp:=TClosableFileStream(fOpenFiles.Remove);
  145. tmp.CloseHandle;
  146. end;
  147. end;
  148. procedure TFileKeeper.NotifyFileOpened(aStream: TClosableFileStream);
  149. begin
  150. fOpenFiles.Add(aStream);
  151. if (fOpenFiles.Count>=fMaxOpen) then
  152. CloseFiles(1);
  153. end;
  154. function TFileKeeper.NotifyOpenFailed(aStream: TClosableFileStream) : boolean;
  155. var
  156. decrement : longint;
  157. begin
  158. Result:=false;
  159. decrement:=round(0.1*fOpenFiles.Count);
  160. if decrement<=0 then exit;
  161. CloseFiles(decrement);
  162. Result:=aStream.RetryOpen;
  163. if Result then
  164. fMaxOpen:=fOpenFiles.Count+1;
  165. end;
  166. constructor TFileKeeper.Create;
  167. begin
  168. fOpenFiles:=TFIFOQueue.Create;
  169. fMaxOpen:=high(longint);
  170. end;
  171. destructor TFileKeeper.Destroy;
  172. begin
  173. fOpenFiles.Free;
  174. end;
  175. var
  176. FileKeeper : TFileKeeper;
  177. { TClosableFileStream }
  178. procedure TClosableFileStream.EnsureHandleOpen;
  179. begin
  180. if fStream<>nil then
  181. exit;
  182. try
  183. fStream:=TFileStream.Create(fFileName,fMode);
  184. except
  185. if not fListener.NotifyOpenFailed(self) then
  186. raise;
  187. end;
  188. fStream.Position:=fPosition;
  189. fListener.NotifyFileOpened(self);
  190. end;
  191. procedure TClosableFileStream.SetSize(const NewSize: Int64);
  192. begin
  193. EnsureHandleOpen;
  194. fStream.Size:=NewSize;
  195. end;
  196. function TClosableFileStream.RetryOpen: boolean;
  197. begin
  198. try
  199. fStream:=TFileStream.Create(fFileName,fMode);
  200. except
  201. Result:=false;
  202. exit;
  203. end;
  204. Result:=true;
  205. end;
  206. constructor TClosableFileStream.Create(const AFileName: String; Mode: Word);
  207. begin
  208. fListener:=FileKeeper;
  209. fFileName:=aFileName;
  210. fMode:=Mode;
  211. fPosition:=0;
  212. fStream:=nil;
  213. EnsureHandleOpen;
  214. //if the file has been created, ensure that further openings don't recreate
  215. //it again!
  216. if fMode=fmCreate then
  217. fMode:=fmOpenReadWrite;
  218. end;
  219. destructor TClosableFileStream.Destroy;
  220. begin
  221. if fStream<>nil then
  222. CloseHandle;
  223. end;
  224. function TClosableFileStream.Read(var Buffer; Count: Longint): Longint;
  225. begin
  226. EnsureHandleOpen;
  227. Result:=fStream.Read(Buffer,Count);
  228. end;
  229. function TClosableFileStream.Write(const Buffer; Count: Longint): Longint;
  230. begin
  231. EnsureHandleOpen;
  232. Result:=fStream.Write(Buffer,Count);
  233. end;
  234. function TClosableFileStream.Seek(const Offset: Int64; Origin: TSeekOrigin
  235. ): Int64;
  236. begin
  237. EnsureHandleOpen;
  238. Result:=fStream.Seek(Offset,Origin);
  239. end;
  240. procedure TClosableFileStream.CloseHandle;
  241. begin
  242. fPosition:=fStream.Position;
  243. fStream.Free;
  244. fStream:=nil;
  245. end;
  246. initialization
  247. FileKeeper:=TFileKeeper.Create;
  248. finalization
  249. FileKeeper.Destroy;
  250. end.