mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-05 11:38:19 +02:00
297 lines
5.8 KiB
ObjectPascal
297 lines
5.8 KiB
ObjectPascal
{
|
|
|
|
FPCRes - Free Pascal Resource Converter
|
|
Part of the Free Pascal distribution
|
|
Copyright (C) 2008-2010 by Giulio Bernardi
|
|
|
|
Support for file streams that can be temporarily closed
|
|
|
|
See the file COPYING, included in this distribution,
|
|
for details about the copyright.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
}
|
|
|
|
unit closablefilestream;
|
|
|
|
{$MODE OBJFPC} {$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils;
|
|
|
|
type
|
|
TClosableFileNotifier = class;
|
|
|
|
{ TClosableFileStream }
|
|
|
|
TClosableFileStream = class(TStream)
|
|
private
|
|
fStream : TFileStream;
|
|
fFileName : string;
|
|
fMode : word;
|
|
fListener : TClosableFileNotifier;
|
|
fPosition : int64;
|
|
procedure EnsureHandleOpen;
|
|
protected
|
|
procedure SetSize(const NewSize: Int64); override;
|
|
function RetryOpen : boolean;
|
|
public
|
|
constructor Create(const AFileName: String; Mode: Word);
|
|
destructor Destroy; override;
|
|
function Read(var Buffer; Count: Longint): Longint; override;
|
|
function Write(const Buffer; Count: Longint): Longint; override;
|
|
function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
|
|
procedure CloseHandle;
|
|
end;
|
|
|
|
TClosableFileNotifier = class
|
|
protected
|
|
procedure NotifyFileOpened(aStream : TClosableFileStream); virtual; abstract;
|
|
function NotifyOpenFailed(aStream: TClosableFileStream) : boolean;
|
|
virtual; abstract;
|
|
end;
|
|
|
|
|
|
implementation
|
|
|
|
type
|
|
PQueueItem = ^TQueueItem;
|
|
TQueueItem = record
|
|
prev : PQueueItem;
|
|
next : PQueueItem;
|
|
data : pointer;
|
|
end;
|
|
|
|
{ TFIFOQueue }
|
|
|
|
TFIFOQueue = class
|
|
private
|
|
fHead : PQueueItem;
|
|
fTail : PQueueItem;
|
|
fCount : integer;
|
|
protected
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Add(p : pointer);
|
|
function Remove : Pointer;
|
|
property Count : integer read fCount;
|
|
end;
|
|
|
|
|
|
{ TFileKeeper }
|
|
|
|
TFileKeeper = class(TClosableFileNotifier)
|
|
private
|
|
fOpenFiles : TFIFOQueue;
|
|
fMaxOpen : longint;
|
|
private
|
|
procedure CloseFiles(const count : integer);
|
|
protected
|
|
procedure NotifyFileOpened(aStream : TClosableFileStream); override;
|
|
function NotifyOpenFailed(aStream: TClosableFileStream) : boolean; override;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
{ TFIFOQueue }
|
|
|
|
constructor TFIFOQueue.Create;
|
|
begin
|
|
fHead:=nil;
|
|
fTail:=nil;
|
|
fCount:=0;
|
|
end;
|
|
|
|
destructor TFIFOQueue.Destroy;
|
|
var
|
|
el : PQueueItem;
|
|
begin
|
|
while fHead<>nil do
|
|
begin
|
|
el:=fHead;
|
|
fHead:=fHead^.next;
|
|
FreeMem(el);
|
|
end;
|
|
end;
|
|
|
|
procedure TFIFOQueue.Add(p: pointer);
|
|
var
|
|
el : PQueueItem;
|
|
begin
|
|
el:=GetMem(sizeof(TQueueItem));
|
|
el^.data:=p;
|
|
el^.prev:=nil;
|
|
el^.next:=fHead;
|
|
if fHead<>nil then
|
|
fHead^.prev:=el;
|
|
fHead:=el;
|
|
if fTail=nil then
|
|
fTail:=fHead;
|
|
inc(fCount);
|
|
end;
|
|
|
|
function TFIFOQueue.Remove: Pointer;
|
|
var
|
|
el : PQueueItem;
|
|
begin
|
|
if fCount=0 then
|
|
begin
|
|
Result:=nil;
|
|
exit;
|
|
end;
|
|
Result:=fTail^.data;
|
|
el:=fTail;
|
|
fTail:=fTail^.prev;
|
|
if fTail=nil then
|
|
fHead:=nil
|
|
else
|
|
fTail^.next:=nil;
|
|
FreeMem(el);
|
|
dec(fCount);
|
|
end;
|
|
|
|
{ TFileKeeper }
|
|
|
|
procedure TFileKeeper.CloseFiles(const count: integer);
|
|
var
|
|
i,max : integer;
|
|
tmp : TClosableFileStream;
|
|
begin
|
|
max:=count;
|
|
if fOpenFiles.Count<max then
|
|
max:=fOpenFiles.Count;
|
|
for i:=0 to max-1 do
|
|
begin
|
|
tmp:=TClosableFileStream(fOpenFiles.Remove);
|
|
tmp.CloseHandle;
|
|
end;
|
|
end;
|
|
|
|
procedure TFileKeeper.NotifyFileOpened(aStream: TClosableFileStream);
|
|
begin
|
|
fOpenFiles.Add(aStream);
|
|
if (fOpenFiles.Count>=fMaxOpen) then
|
|
CloseFiles(1);
|
|
end;
|
|
|
|
function TFileKeeper.NotifyOpenFailed(aStream: TClosableFileStream) : boolean;
|
|
var
|
|
decrement : longint;
|
|
begin
|
|
Result:=false;
|
|
decrement:=round(0.1*fOpenFiles.Count);
|
|
if decrement<=0 then exit;
|
|
CloseFiles(decrement);
|
|
Result:=aStream.RetryOpen;
|
|
if Result then
|
|
fMaxOpen:=fOpenFiles.Count+1;
|
|
end;
|
|
|
|
constructor TFileKeeper.Create;
|
|
begin
|
|
fOpenFiles:=TFIFOQueue.Create;
|
|
fMaxOpen:=high(longint);
|
|
end;
|
|
|
|
destructor TFileKeeper.Destroy;
|
|
begin
|
|
fOpenFiles.Free;
|
|
end;
|
|
|
|
var
|
|
FileKeeper : TFileKeeper;
|
|
|
|
{ TClosableFileStream }
|
|
|
|
procedure TClosableFileStream.EnsureHandleOpen;
|
|
begin
|
|
if fStream<>nil then
|
|
exit;
|
|
try
|
|
fStream:=TFileStream.Create(fFileName,fMode);
|
|
except
|
|
if not fListener.NotifyOpenFailed(self) then
|
|
raise;
|
|
end;
|
|
fStream.Position:=fPosition;
|
|
fListener.NotifyFileOpened(self);
|
|
end;
|
|
|
|
procedure TClosableFileStream.SetSize(const NewSize: Int64);
|
|
begin
|
|
EnsureHandleOpen;
|
|
fStream.Size:=NewSize;
|
|
end;
|
|
|
|
function TClosableFileStream.RetryOpen: boolean;
|
|
begin
|
|
try
|
|
fStream:=TFileStream.Create(fFileName,fMode);
|
|
except
|
|
Result:=false;
|
|
exit;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
constructor TClosableFileStream.Create(const AFileName: String; Mode: Word);
|
|
begin
|
|
fListener:=FileKeeper;
|
|
fFileName:=aFileName;
|
|
fMode:=Mode;
|
|
fPosition:=0;
|
|
fStream:=nil;
|
|
EnsureHandleOpen;
|
|
//if the file has been created, ensure that further openings don't recreate
|
|
//it again!
|
|
if fMode=fmCreate then
|
|
fMode:=fmOpenReadWrite;
|
|
end;
|
|
|
|
destructor TClosableFileStream.Destroy;
|
|
begin
|
|
if fStream<>nil then
|
|
CloseHandle;
|
|
end;
|
|
|
|
function TClosableFileStream.Read(var Buffer; Count: Longint): Longint;
|
|
begin
|
|
EnsureHandleOpen;
|
|
Result:=fStream.Read(Buffer,Count);
|
|
end;
|
|
|
|
function TClosableFileStream.Write(const Buffer; Count: Longint): Longint;
|
|
begin
|
|
EnsureHandleOpen;
|
|
Result:=fStream.Write(Buffer,Count);
|
|
end;
|
|
|
|
function TClosableFileStream.Seek(const Offset: Int64; Origin: TSeekOrigin
|
|
): Int64;
|
|
begin
|
|
EnsureHandleOpen;
|
|
Result:=fStream.Seek(Offset,Origin);
|
|
end;
|
|
|
|
procedure TClosableFileStream.CloseHandle;
|
|
begin
|
|
fPosition:=fStream.Position;
|
|
fStream.Free;
|
|
fStream:=nil;
|
|
end;
|
|
|
|
initialization
|
|
FileKeeper:=TFileKeeper.Create;
|
|
|
|
finalization
|
|
FileKeeper.Destroy;
|
|
|
|
end.
|
|
|