FastReport_2022_VCL/Source/ClientServer/frxServerCache.pas
2024-01-01 16:13:08 +01:00

762 lines
19 KiB
ObjectPascal

{******************************************}
{ }
{ FastReport VCL }
{ Server cahce module }
{ }
{ Copyright (c) 1998-2021 }
{ by Fast Reports Inc. }
{ }
{******************************************}
unit frxServerCache;
{$I frx.inc}
interface
uses
{$IFNDEF Linux}
Windows,
{$ENDIF}
{$IFDEF LCLGTK2}
gdk2,
{$ENDIF}
Classes, SysUtils, frxUtils, frxServerUtils, frxNetUtils,
frxVariables, frxClass, frxServerLog, SyncObjs;
type
TfrxServerCacheItem = class(TCollectionItem)
private
FReportName: String;
FVariables: TfrxVariables;
FFileName: String;
FExpTime: TDateTime;
FSessionId: String;
FStream: TStream;
FInternalId: String;
FFileType: TfrxServerFormat;
public
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
published
property ReportName: String read FReportName write FReportName;
property Variables: TfrxVariables read FVariables write FVariables;
property FileName: String read FFileName write FFileName;
property FileType: TfrxServerFormat read FFileType write FFileType;
property ExpTime: TDateTime read FExpTime write FExpTime;
property SessionId: String read FSessionId write FSessionId;
property Stream: TStream read FStream write FStream;
property InternalId: String read FInternalId write FInternalId;
end;
TfrxServerCacheSpool = class(TCollection)
private
function GetItems(Index: Integer): TfrxServerCacheItem;
function EqualVariables(const Var1: TfrxVariables; const Var2: TfrxVariables): Boolean;
public
constructor Create;
destructor Destroy; override;
property Items[Index: Integer]: TfrxServerCacheItem read GetItems;
procedure Clear;
published
function Add: TfrxServerCacheItem;
function Insert(Index: Integer): TfrxServerCacheItem;
function IndexOf(const ReportName: String; const Variables: TfrxVariables; const SessionId: String; const aFileFormat: TfrxServerFormat): Integer;
end;
TfrxServerCache = class (TThread)
protected
FCACHE_PREFIX: String;
FCachePath: String;
FActive: Boolean;
FLatency: Integer;
FHeap: TfrxServerCacheSpool;
FMemoryCache: Boolean;
FThreadActive: Boolean;
procedure SetActive(const Value: Boolean);
procedure CleanUpFiles; virtual;
procedure RemoveExpired; virtual;
procedure Execute; override;
public
constructor Create(WithInitialization: Boolean = True);
destructor Destroy; override;
function GetCachedStream(const Report: TfrxReport; const ReportName: String; const Variables: TfrxVariables; const Id: String): Boolean; overload;
function GetCachedReportById(const Report: TfrxReport; const Id: String): Boolean;
function IsReportInCache(const ReportName: String; const Variables: TfrxVariables; const Id: String; const aFileFormat: TfrxServerFormat = sfFRP): Boolean;
function CopyCacheFileIfExist(const aFileName: String; const ReportName: String; const Variables: TfrxVariables; const Id: String; const aFileFormat: TfrxServerFormat = sfFRP): Boolean;
procedure Open;
procedure Close;
procedure Clear;
procedure AddReport(const Report: TfrxReport;
const ReportName: String; const Variables: TfrxVariables; const Id: String; const InternalId: String);
procedure AddFileFormat(const sStream: TStream; const aFileFormat: TfrxServerFormat;
const ReportName: String; const Variables: TfrxVariables; const Id: String; const InternalId: String); virtual;
property Active: Boolean read FActive write SetActive;
property CachePath: String read FCachePath write FCachePath;
property DefaultLatency: Integer read FLatency write FLatency;
property Heap: TfrxServerCacheSpool read FHeap;
property MemoryCache: Boolean read FMemoryCache write FMemoryCache;
property CACHE_PREFIX: String read FCACHE_PREFIX write FCACHE_PREFIX;
end;
TfrxResultCache = class (TfrxServerCache) //for caching fp3 export results
protected
procedure CleanUpFiles; override;
public
constructor Create(aCachePath, aCACHE_PREFIX: String); overload;
procedure AddFileFormat(const sStream: TStream; const aFileFormat: TfrxServerFormat;
const ReportName: String; const Variables: TfrxVariables; const Id: String; const InternalId: String); override;
procedure RemoveExpired; override;
procedure UpdExpTime(const ReportName: String; const Variables: TfrxVariables; const SessionId: String; const aFileFormat: TfrxServerFormat);
end;
var
CacheCS1: TCriticalSection;
ReportCache: TfrxServerCache;
ResultCache: TfrxResultCache;
procedure RemoveAll(path: string);
function GetFileCount(Dir: string): Integer;
implementation
uses frxFileUtils, frxServerConfig, frxServerReportsList, frxIOTransportIntf;
procedure Lock();
begin
{$IFDEF LCLGTK2}
gdk_threads_enter;
{$ENDIF}
end;
procedure Unlock();
begin
{$IFDEF LCLGTK2}
gdk_threads_leave;
{$ENDIF}
end;
{ TfrxServerCacheSpool }
function TfrxServerCacheSpool.Add: TfrxServerCacheItem;
begin
Result := TfrxServerCacheItem.Create(Self);
end;
procedure TfrxServerCacheSpool.Clear;
var
i: Integer;
begin
CacheCS1.Enter;
try
for i := 0 to Count - 1 do
if Assigned(Items[i].Stream) then
begin
Items[i].Stream.Free;
Items[i].Stream := nil;
end;
finally
CacheCS1.Leave;
end;
inherited Clear;
end;
constructor TfrxServerCacheSpool.Create;
begin
inherited Create(TfrxServerCacheItem);
end;
destructor TfrxServerCacheSpool.Destroy;
begin
inherited;
end;
function TfrxServerCacheSpool.EqualVariables(const Var1,
Var2: TfrxVariables): Boolean;
var
i, j, k: Integer;
begin
Result := False;
if Assigned(Var1) and Assigned(Var2) then
begin
j := Var1.Count;
if j = Var2.Count then
begin
Result := True;
for i := 0 to j - 1 do
begin
k := Var2.IndexOf(Var1.Items[i].Name);
if (k = -1) or (Var2.Items[k].Value <> Var1.Items[i].Value) then
begin
Result := False;
Break;
end;
end;
end;
end
else if Var1 = Var2 then
Result := True;
end;
function TfrxServerCacheSpool.GetItems(Index: Integer): TfrxServerCacheItem;
begin
Result := TfrxServerCacheItem(inherited Items[Index]);
end;
function TfrxServerCacheSpool.IndexOf(const ReportName: String;
const Variables: TfrxVariables; const SessionId: String; const
aFileFormat: TfrxServerFormat): Integer;
var
i: Integer;
s: String;
begin
Result := -1;
for i := 0 to Count - 1 do
begin
s := Items[i].SessionId;
if ((aFileFormat = Items[i].FileType) and
(AnsiCompareText(ReportName, Items[i].ReportName) = 0) and
EqualVariables(Items[i].Variables, Variables)) and (s = '') or
((AnsiCompareText(SessionId, s) = 0) and (s <> '')) then
begin
Result := i;
break;
end;
end;
end;
function TfrxServerCacheSpool.Insert(Index: Integer): TfrxServerCacheItem;
begin
Result := TfrxServerCacheItem(inherited Insert(Index));
end;
{ TfrxServerCacheItem }
constructor TfrxServerCacheItem.Create(Collection: TCollection);
begin
inherited Create(Collection);
FStream := nil;
FFileType := sfFRP;
end;
destructor TfrxServerCacheItem.Destroy;
begin
CacheCS1.Enter;
try
if Assigned(FStream) then
FStream.Free;
if Assigned(FVariables) then
FVariables.Free;
finally
CacheCS1.Leave;
end;
inherited;
end;
{ TfrxServerCache }
procedure TfrxServerCache.AddReport(const Report: TfrxReport;
const ReportName: String; const Variables: TfrxVariables; const Id: String; const InternalId: String);
var
Item: TfrxServerCacheItem;
Lat: TDateTime;
begin
if Active then
begin
CacheCS1.Enter;
try
Lat := ReportsList.GetCacheLatency(ReportName) / 86400;
if Lat > 0 then
begin
Item := FHeap.Add;
Item.ReportName := ReportName;
Item.InternalId := InternalId;
if Assigned(Variables) then
begin
Item.Variables := TfrxVariables.Create;
Item.Variables.Assign(Variables);
end;
Item.ExpTime := Now + Lat;
if Id <> '' then
Item.ExpTime := Item.ExpTime * 20;
if FMemoryCache then
begin
Item.Stream := TMemoryStream.Create;
try
Report.PreviewPages.SaveToStream(Item.Stream);
Item.Stream.Position := 0;
except
Item.Stream.Free;
FMemoryCache := False;
end;
end;
if not FMemoryCache then
begin
Item.FileName := GetUniqueFileName(FCachePath, CACHE_PREFIX);
try
Report.PreviewPages.SaveToFile(Item.FileName);
except
Active := False;
end;
end;
Item.SessionId := Id;
end;
finally
CacheCS1.Leave;
end;
end;
end;
procedure TfrxServerCache.CleanUpFiles;
var
SRec: TSearchRec;
i: Integer;
begin
if DirectoryExists(FCachePath) then
begin
i := FindFirst(FCachePath + CACHE_PREFIX + '*.*', 0, SRec);
try
while i = 0 do
begin
try
DeleteFile(FCachePath + SRec.Name);
except
end;
i := FindNext(SRec);
end;
finally
FindClose(SRec);
end;
end;
end;
procedure TfrxServerCache.Clear;
begin
FHeap.Clear;
CleanUpFiles;
end;
procedure TfrxServerCache.Close;
begin
if FActive then
begin
Suspend;
Clear;
FActive := False;
end;
end;
function TfrxServerCache.CopyCacheFileIfExist(const aFileName,
ReportName: String; const Variables: TfrxVariables; const Id: String;
const aFileFormat: TfrxServerFormat): Boolean;
var
FileIn , FileOut: TFileStream;
begin
Result := False;
if Active then
begin
CacheCS1.Enter;
try
if FHeap.IndexOf(ReportName, Variables, Id, aFileFormat) <> - 1 then
begin
FileOut := TFileStream.Create(aFileName, fmCreate);
FileIn := TFileStream.Create(ReportCache.CachePath + CACHE_PREFIX + ReportName, fmOpenRead or fmShareDenyWrite);
try
FileIn.Position := 0;
FileOut.CopyFrom(FileIn, FileIn.Size);
Result := FileIn.Size > 0;
finally
FileIn.Free;
FileOut.Free;
end;
end;
finally
CacheCS1.Leave;
end;
end;
if Result then
LogWriter.StatAddCacheHit;
end;
constructor TfrxServerCache.Create(WithInitialization: Boolean = True);
begin
inherited Create(True);
FMemoryCache := ServerConfig.GetValue('server.cache.target') = 'memory';
FActive := ServerConfig.GetValue('server.cache.active') = 'yes';
FLatency := StrToInt(ServerConfig.GetValue('server.cache.defaultlatency'));
FHeap := TfrxServerCacheSpool.Create;
if (WithInitialization) then
begin
FCachePath := frxGetAbsPathDir(ServerConfig.GetValue('server.cache.path'), ServerConfig.ConfigFolder);
FCACHE_PREFIX := '$fr_';
end;
CleanUpFiles;
Resume;
end;
destructor TfrxServerCache.Destroy;
begin
Clear;
Terminate;
PMessages;
while FThreadActive do
Sleep(10);
FHeap.Free;
inherited;
end;
procedure TfrxServerCache.Execute;
var
i: Integer;
begin
FThreadActive := True;
while not Terminated do
begin
RemoveExpired;
i := 0;
while (not Terminated) and (i < 100) do
begin
Sleep(100);
Inc(i);
PMessages;
end;
end;
FThreadActive := False;
end;
function TfrxServerCache.GetCachedStream(const Report: TfrxReport;
const ReportName: String; const Variables: TfrxVariables; const Id: String): Boolean;
var
i: Integer;
begin
Result := False;
if Active then
begin
CacheCS1.Enter;
try
i := FHeap.IndexOf(ReportName, Variables, Id, sfFRP);
if i <> -1 then
begin
try
FHeap.Items[i].ExpTime := Now + StrToInt(ServerConfig.GetValue('server.cache.defaultlatency')) / 86400;
if Assigned(FHeap.Items[i].Stream) then
begin
FHeap.Items[i].Stream.Position := 0;
try
Lock;
Report.PreviewPages.LoadFromStream(FHeap.Items[i].Stream);
finally
Unlock;
end;
Result := True;
end
else if FileExists(FHeap.Items[i].FileName) then
begin
try
Lock;
Report.PreviewPages.LoadFromFile(FHeap.Items[i].FileName);
finally
Unlock;
end;
Result := True;
end;
except
on e: Exception do
begin
LogWriter.Write(ERROR_LEVEL, DateTimeToStr(Now) + #9 + Id + #9'cache read error: ' + FHeap.Items[i].FileName + ' ' + Report.Errors.Text + e.Message);
LogWriter.ErrorReached;
Result := False;
end;
end;
end
finally
CacheCS1.Leave;
end;
end;
if Result then
LogWriter.StatAddCacheHit;
end;
function TfrxServerCache.IsReportInCache(const ReportName: String;
const Variables: TfrxVariables; const Id: String; const aFileFormat: TfrxServerFormat = sfFRP): Boolean;
var
i: Integer;
begin
Result := False;
if Active then
begin
CacheCS1.Enter;
try
i := FHeap.IndexOf(ReportName, Variables, Id, aFileFormat);
if i <> - 1 then
begin
FHeap.Items[i].ExpTime := Now + StrToInt(ServerConfig.GetValue('server.cache.defaultlatency')) / 86400;
Result := True;
end;
finally
CacheCS1.Leave;
end;
end;
if Result then
LogWriter.StatAddCacheHit;
end;
procedure TfrxServerCache.Open;
begin
if not FActive then
begin
Resume;
FActive := True;
end;
end;
procedure TfrxServerCache.RemoveExpired;
var
i: Integer;
begin
i := 0;
CacheCS1.Enter;
try
while i < FHeap.Count do
begin
if FHeap.Items[i].ExpTime <= Now then
begin
if Assigned(FHeap.Items[i].Stream) then
begin
FHeap.Items[i].Stream.Free;
FHeap.Items[i].Stream := nil;
end;
if FileExists(FHeap.Items[i].FileName) then
DeleteFile(FHeap.Items[i].FileName);
FHeap.Items[i].Free; // Delete(i);
end else Inc(i);
end;
finally
CacheCS1.Leave;
end;
end;
procedure TfrxServerCache.SetActive(const Value: Boolean);
begin
if Value <> FActive then
if Value then Open
else Close;
end;
function TfrxServerCache.GetCachedReportById(const Report: TfrxReport;
const Id: String): Boolean;
var
i: Integer;
begin
Result := False;
if Active then
begin
CacheCS1.Enter;
try
for i := 0 to FHeap.Count - 1 do
begin
if FHeap.Items[i].InternalId = Id then
begin
try
if Assigned(FHeap.Items[i].Stream) then
begin
FHeap.Items[i].Stream.Position := 0;
try
Lock;
Report.PreviewPages.LoadFromStream(FHeap.Items[i].Stream);
finally
Unlock;
end;
//Result := True;
end
else if FileExists(FHeap.Items[i].FileName) then
begin
try
Lock;
Report.PreviewPages.LoadFromFile(FHeap.Items[i].FileName);
finally
Unlock;
end;
//Result := True;
end;
except
on e: Exception do
begin
LogWriter.Write(ERROR_LEVEL, DateTimeToStr(Now) + #9 + Id + #9'cache read error: ' + FHeap.Items[i].FileName + ' ' + Report.Errors.Text + e.Message);
LogWriter.ErrorReached;
//Result := False;
end;
end;
Result := True;
break;
end;
end;
finally
CacheCS1.Leave;
end;
end;
if Result then
LogWriter.StatAddCacheHit;
end;
procedure TfrxServerCache.AddFileFormat(const sStream: TStream;
const aFileFormat: TfrxServerFormat; const ReportName: String;
const Variables: TfrxVariables; const Id, InternalId: String);
begin
//
end;
{TfrxResultCache}
constructor TfrxResultCache.Create(aCachePath, aCACHE_PREFIX: String);
begin
FCachePath := aCachePath;
FCACHE_PREFIX := aCACHE_PREFIX;
inherited Create(False);
end;
procedure TfrxResultCache.CleanUpFiles;
var
searchResult: TSearchRec;
begin
if FindFirst(FCachePath + CACHE_PREFIX + '*', faDirectory, searchResult) = 0 then
begin
repeat
RemoveAll(FCachePath + searchResult.Name);
until FindNext(searchResult) <> 0;
FindClose(searchResult);
end;
end;
procedure TfrxResultCache.AddFileFormat(const sStream: TStream; const aFileFormat: TfrxServerFormat;
const ReportName: String; const Variables: TfrxVariables; const Id: String; const InternalId: String);
var
Item: TfrxServerCacheItem;
Lat: TDateTime;
begin
if Active then
begin
CacheCS1.Enter;
try
Lat := ReportsList.GetCacheLatency(ReportName) / 86400;
if Lat > 0 then
begin
Item := FHeap.Add;
Item.FileType := aFileFormat;
Item.ReportName := ReportName;
Item.InternalId := InternalId;
if Assigned(Variables) then
begin
Item.Variables := TfrxVariables.Create;
Item.Variables.Assign(Variables);
end;
Item.ExpTime := Now + Lat;
Item.FileName := FCachePath + PathDelim + Id + PathDelim+ ReportName;
Item.ReportName := ReportName;
Item.SessionId := Id;
sStream.Position := 0;
end;
finally
CacheCS1.Leave;
end;
end;
end;
procedure TfrxResultCache.RemoveExpired;
var
i: Integer;
folder: String;
begin
i := 0;
CacheCS1.Enter;
try
while i < FHeap.Count do
begin
if FHeap.Items[i].ExpTime <= Now then
begin
if FileExists(FHeap.Items[i].FileName) then
begin
DeleteFile(FHeap.Items[i].FileName);
folder := ExtractFileDir(FHeap.Items[i].FileName);
if GetFileCount(folder) = 0 then
RemoveDir(folder);
end;
FHeap.Items[i].Free; // Delete(i);
end else Inc(i);
end;
finally
CacheCS1.Leave;
end;
end;
procedure TfrxResultCache.UpdExpTime(const ReportName: String; const Variables: TfrxVariables; const SessionId: String; const aFileFormat: TfrxServerFormat);
var
i: Integer;
begin
if Active then
begin
CacheCS1.Enter;
try
i := FHeap.IndexOf(ReportName, Variables, SessionId, aFileFormat);
if i <> -1 then
begin
FHeap.Items[i].ExpTime := Now + StrToInt(ServerConfig.GetValue('server.cache.defaultlatency')) / 86400;
end
finally
CacheCS1.Leave;
end;
end;
end;
{other function}
procedure RemoveAll(path: string);
var
sr: TSearchRec;
begin
if FindFirst(path + PathDelim + '*.*', faAnyFile, sr) = 0 then
begin
repeat
if sr.Attr and faDirectory = 0 then
begin
DeleteFile(path + PathDelim + sr.name);
end
else
begin
if pos('.', sr.name) <= 0 then
RemoveAll(path + PathDelim + sr.name);
end;
until
FindNext(sr) <> 0;
end;
FindClose(sr);
{$IFNDEF FPC}
RemoveDirectory(PChar(path));
{$ELSE}
DeleteFolder(path);
{$ENDIF}
end;
function GetFileCount(Dir: string): Integer;
var
fs: TSearchRec;
begin
Result:=0;
if FindFirst(Dir + PathDelim + '*.*',faAnyFile - faDirectory - faVolumeID, fs) = 0 then
repeat
inc(Result);
until FindNext(fs) <> 0;
FindClose(fs);
end;
initialization
CacheCS1 := TCriticalSection.Create;
finalization
CacheCS1.Free;
end.