closablefilestream.pas 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  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(NewSize: Longint); override;
  30. procedure SetSize(const NewSize: Int64); override;
  31. function RetryOpen : boolean;
  32. public
  33. constructor Create(const AFileName: String; Mode: Word);
  34. destructor Destroy; override;
  35. function Read(var Buffer; Count: Longint): Longint; override;
  36. function Write(const Buffer; Count: Longint): Longint; override;
  37. function Seek(Offset: Longint; Origin: Word): Longint; override;
  38. function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
  39. procedure CloseHandle;
  40. end;
  41. TClosableFileNotifier = class
  42. protected
  43. procedure NotifyFileOpened(aStream : TClosableFileStream); virtual; abstract;
  44. function NotifyOpenFailed(aStream: TClosableFileStream) : boolean;
  45. virtual; abstract;
  46. end;
  47. implementation
  48. type
  49. PQueueItem = ^TQueueItem;
  50. TQueueItem = record
  51. prev : PQueueItem;
  52. next : PQueueItem;
  53. data : pointer;
  54. end;
  55. { TFIFOQueue }
  56. TFIFOQueue = class
  57. private
  58. fHead : PQueueItem;
  59. fTail : PQueueItem;
  60. fCount : integer;
  61. protected
  62. public
  63. constructor Create;
  64. destructor Destroy; override;
  65. procedure Add(p : pointer);
  66. function Remove : Pointer;
  67. property Count : integer read fCount;
  68. end;
  69. { TFileKeeper }
  70. TFileKeeper = class(TClosableFileNotifier)
  71. private
  72. fOpenFiles : TFIFOQueue;
  73. fMaxOpen : longint;
  74. private
  75. procedure CloseFiles(const count : integer);
  76. protected
  77. procedure NotifyFileOpened(aStream : TClosableFileStream); override;
  78. function NotifyOpenFailed(aStream: TClosableFileStream) : boolean; override;
  79. public
  80. constructor Create;
  81. destructor Destroy; override;
  82. end;
  83. { TFIFOQueue }
  84. constructor TFIFOQueue.Create;
  85. begin
  86. fHead:=nil;
  87. fTail:=nil;
  88. fCount:=0;
  89. end;
  90. destructor TFIFOQueue.Destroy;
  91. var
  92. el : PQueueItem;
  93. begin
  94. while fHead<>nil do
  95. begin
  96. el:=fHead;
  97. fHead:=fHead^.next;
  98. FreeMem(el);
  99. end;
  100. end;
  101. procedure TFIFOQueue.Add(p: pointer);
  102. var
  103. el : PQueueItem;
  104. begin
  105. el:=GetMem(sizeof(TQueueItem));
  106. el^.data:=p;
  107. el^.prev:=nil;
  108. el^.next:=fHead;
  109. if fHead<>nil then
  110. fHead^.prev:=el;
  111. fHead:=el;
  112. if fTail=nil then
  113. fTail:=fHead;
  114. inc(fCount);
  115. end;
  116. function TFIFOQueue.Remove: Pointer;
  117. var
  118. el : PQueueItem;
  119. begin
  120. if fCount=0 then
  121. begin
  122. Result:=nil;
  123. exit;
  124. end;
  125. Result:=fTail^.data;
  126. el:=fTail;
  127. fTail:=fTail^.prev;
  128. if fTail=nil then
  129. fHead:=nil
  130. else
  131. fTail^.next:=nil;
  132. FreeMem(el);
  133. dec(fCount);
  134. end;
  135. { TFileKeeper }
  136. procedure TFileKeeper.CloseFiles(const count: integer);
  137. var
  138. i,max : integer;
  139. tmp : TClosableFileStream;
  140. begin
  141. max:=count;
  142. if fOpenFiles.Count<max then
  143. max:=fOpenFiles.Count;
  144. for i:=0 to max-1 do
  145. begin
  146. tmp:=TClosableFileStream(fOpenFiles.Remove);
  147. tmp.CloseHandle;
  148. end;
  149. end;
  150. procedure TFileKeeper.NotifyFileOpened(aStream: TClosableFileStream);
  151. begin
  152. fOpenFiles.Add(aStream);
  153. if (fOpenFiles.Count>=fMaxOpen) then
  154. CloseFiles(1);
  155. end;
  156. function TFileKeeper.NotifyOpenFailed(aStream: TClosableFileStream) : boolean;
  157. var
  158. decrement : longint;
  159. begin
  160. Result:=false;
  161. decrement:=round(0.1*fOpenFiles.Count);
  162. if decrement<=0 then exit;
  163. CloseFiles(decrement);
  164. Result:=aStream.RetryOpen;
  165. if Result then
  166. fMaxOpen:=fOpenFiles.Count+1;
  167. end;
  168. constructor TFileKeeper.Create;
  169. begin
  170. fOpenFiles:=TFIFOQueue.Create;
  171. fMaxOpen:=high(longint);
  172. end;
  173. destructor TFileKeeper.Destroy;
  174. begin
  175. fOpenFiles.Free;
  176. end;
  177. var
  178. FileKeeper : TFileKeeper;
  179. { TClosableFileStream }
  180. procedure TClosableFileStream.EnsureHandleOpen;
  181. begin
  182. if fStream<>nil then
  183. exit;
  184. try
  185. fStream:=TFileStream.Create(fFileName,fMode);
  186. except
  187. if not fListener.NotifyOpenFailed(self) then
  188. raise;
  189. end;
  190. fStream.Position:=fPosition;
  191. fListener.NotifyFileOpened(self);
  192. end;
  193. procedure TClosableFileStream.SetSize(NewSize: Longint);
  194. begin
  195. SetSize(int64(NewSize));
  196. end;
  197. procedure TClosableFileStream.SetSize(const NewSize: Int64);
  198. begin
  199. EnsureHandleOpen;
  200. fStream.Size:=NewSize;
  201. end;
  202. function TClosableFileStream.RetryOpen: boolean;
  203. begin
  204. try
  205. fStream:=TFileStream.Create(fFileName,fMode);
  206. except
  207. Result:=false;
  208. exit;
  209. end;
  210. Result:=true;
  211. end;
  212. constructor TClosableFileStream.Create(const AFileName: String; Mode: Word);
  213. begin
  214. fListener:=FileKeeper;
  215. fFileName:=aFileName;
  216. fMode:=Mode;
  217. fPosition:=0;
  218. fStream:=nil;
  219. EnsureHandleOpen;
  220. //if the file has been created, ensure that further openings don't recreate
  221. //it again!
  222. if fMode=fmCreate then
  223. fMode:=fmOpenReadWrite;
  224. end;
  225. destructor TClosableFileStream.Destroy;
  226. begin
  227. if fStream<>nil then
  228. CloseHandle;
  229. end;
  230. function TClosableFileStream.Read(var Buffer; Count: Longint): Longint;
  231. begin
  232. EnsureHandleOpen;
  233. Result:=fStream.Read(Buffer,Count);
  234. end;
  235. function TClosableFileStream.Write(const Buffer; Count: Longint): Longint;
  236. begin
  237. EnsureHandleOpen;
  238. Result:=fStream.Write(Buffer,Count);
  239. end;
  240. function TClosableFileStream.Seek(Offset: Longint; Origin: Word): Longint;
  241. begin
  242. EnsureHandleOpen;
  243. Result:=fStream.Seek(Offset,Origin);
  244. end;
  245. function TClosableFileStream.Seek(const Offset: Int64; Origin: TSeekOrigin
  246. ): Int64;
  247. begin
  248. EnsureHandleOpen;
  249. Result:=fStream.Seek(Offset,Origin);
  250. end;
  251. procedure TClosableFileStream.CloseHandle;
  252. begin
  253. fPosition:=fStream.Position;
  254. fStream.Free;
  255. fStream:=nil;
  256. end;
  257. initialization
  258. FileKeeper:=TFileKeeper.Create;
  259. finalization
  260. FileKeeper.Destroy;
  261. end.