integrated TFuture
This commit is contained in:
@@ -41,9 +41,7 @@ object ChartForm: TChartForm
|
||||
Height = 23
|
||||
Anchors = [akLeft, akTop, akRight]
|
||||
TabOrder = 0
|
||||
Text =
|
||||
'C:\Users\Brummel\Documents\cAlgo\TickData\Pepperstone\EURUSD_201' +
|
||||
'7.tab'
|
||||
Text = '\\COFFEE\TickData\Pepperstone\EURUSD_2017_02.tab_zip'
|
||||
end
|
||||
object LoadButton: TButton
|
||||
Left = 686
|
||||
|
||||
@@ -543,12 +543,13 @@ var
|
||||
InitialDirectory: string;
|
||||
SelectedPath, SelectedSymbol: string;
|
||||
SelectedYear: Integer;
|
||||
SelectedMonth: Integer;
|
||||
IsValidSyntax: Boolean;
|
||||
begin
|
||||
OpenDialog := TOpenDialog.Create( Self );
|
||||
try
|
||||
OpenDialog.Title := 'Open Tick Data File';
|
||||
OpenDialog.Filter := 'Tick Data Files (*.tab)|*.TAB|All Files (*.*)|*.*';
|
||||
OpenDialog.Filter := 'Tick Data Files (*.tab)|*.tab;*.tab_zip;*.tab_live|All Files (*.*)|*.*';
|
||||
OpenDialog.DefaultExt := 'tab';
|
||||
OpenDialog.Options := [ofFileMustExist, ofPathMustExist, ofHideReadOnly, ofEnableSizing];
|
||||
|
||||
@@ -574,11 +575,11 @@ begin
|
||||
Memo.Lines.Add( FormatDateTime( 'yyyy-mm-dd hh:nn:ss', Now ) + ' - File selected via dialog: ' + OpenDialog.FileName );
|
||||
|
||||
IsValidSyntax := Myc.TickLoader.TryParseFileName( OpenDialog.FileName,
|
||||
SelectedPath, SelectedSymbol, SelectedYear );
|
||||
SelectedPath, SelectedSymbol, SelectedYear, SelectedMonth );
|
||||
|
||||
if not IsValidSyntax then
|
||||
begin
|
||||
MessageDlg( Format( 'Warning: The selected filename ''%s'' does not match the expected format (Prefix_Symbol_Year.tab).',
|
||||
MessageDlg( Format( 'Warning: The selected filename ''%s'' does not match the expected format (Prefix_Symbol_Year_Month.tab).',
|
||||
[System.IOUtils.TPath.GetFileName( OpenDialog.FileName )] ), mtWarning, [mbOK], 0 );
|
||||
Memo.Lines.Add( 'Warning: Incorrect filename syntax chosen by user.' );
|
||||
end;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
program ChartDisplayProject;
|
||||
|
||||
uses
|
||||
FastMM5,
|
||||
Vcl.Forms,
|
||||
ChartDisplayMain in 'ChartDisplayMain.pas' {ChartForm},
|
||||
Myc.OHLCCache in '..\common\Myc.OHLCCache.pas',
|
||||
Myc.ChartDataController in '..\common\Myc.ChartDataController.pas';
|
||||
Myc.ChartDataController in '..\common\Myc.ChartDataController.pas',
|
||||
DataStreamer in 'DataStreamer.pas';
|
||||
|
||||
{$R *.res}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
<UWP_DelphiLogo44>$(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png</UWP_DelphiLogo44>
|
||||
<UWP_DelphiLogo150>$(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png</UWP_DelphiLogo150>
|
||||
<SanitizedProjectName>ChartDisplayProject</SanitizedProjectName>
|
||||
<DCC_UnitSearchPath>..\common;$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
|
||||
<DCC_UnitSearchPath>..\common;T:\Myc\Src;$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
|
||||
<VerInfo_Locale>1031</VerInfo_Locale>
|
||||
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
|
||||
</PropertyGroup>
|
||||
@@ -110,6 +110,9 @@
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
|
||||
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
|
||||
<DCC_RunTimeTypeInfo>true</DCC_RunTimeTypeInfo>
|
||||
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
|
||||
<VerInfo_Locale>1033</VerInfo_Locale>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_2)'!=''">
|
||||
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
|
||||
@@ -129,10 +132,10 @@
|
||||
</DelphiCompile>
|
||||
<DCCReference Include="ChartDisplayMain.pas">
|
||||
<Form>ChartForm</Form>
|
||||
<FormType>dfm</FormType>
|
||||
</DCCReference>
|
||||
<DCCReference Include="..\common\Myc.OHLCCache.pas"/>
|
||||
<DCCReference Include="..\common\Myc.ChartDataController.pas"/>
|
||||
<DCCReference Include="DataStreamer.pas"/>
|
||||
<BuildConfiguration Include="Base">
|
||||
<Key>Base</Key>
|
||||
</BuildConfiguration>
|
||||
@@ -153,12 +156,7 @@
|
||||
<Source>
|
||||
<Source Name="MainSource">ChartDisplayProject.dpr</Source>
|
||||
</Source>
|
||||
<Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDSBIN)\bcboffice2k290.bpl">Embarcadero C++Builder Office 2000 Servers Package</Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDSBIN)\bcbofficexp290.bpl">Embarcadero C++Builder Office XP Servers Package</Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDSBIN)\dcloffice2k290.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
|
||||
<Excluded_Packages Name="$(BDSBIN)\dclofficexp290.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
|
||||
</Excluded_Packages>
|
||||
<Excluded_Packages/>
|
||||
</Delphi.Personality>
|
||||
<Deployment Version="5">
|
||||
<DeployFile LocalName="Win32\Debug\ChartDisplayProject.exe" Configuration="Debug" Class="ProjectOutput">
|
||||
@@ -173,9 +171,9 @@
|
||||
<Overwrite>true</Overwrite>
|
||||
</Platform>
|
||||
</DeployFile>
|
||||
<DeployFile LocalName="Win64\Debug\ChartDisplayProject.rsm" Configuration="Debug" Class="DebugSymbols">
|
||||
<DeployFile LocalName="Win64\Release\ChartDisplayProject.exe" Configuration="Release" Class="ProjectOutput">
|
||||
<Platform Name="Win64">
|
||||
<RemoteName>ChartDisplayProject.rsm</RemoteName>
|
||||
<RemoteName>ChartDisplayProject.exe</RemoteName>
|
||||
<Overwrite>true</Overwrite>
|
||||
</Platform>
|
||||
</DeployFile>
|
||||
|
||||
@@ -0,0 +1,671 @@
|
||||
unit DataStreamer;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils, System.Classes, System.Generics.Collections, System.IOUtils, System.Threading,
|
||||
System.Zip;
|
||||
|
||||
type
|
||||
IDataPage = interface
|
||||
['{B3E3C5B7-7C1A-4A8B-8B7E-15C9D9E0F4A6}']
|
||||
function GetStream: TStream;
|
||||
function GetSourceIdentifier: string;
|
||||
function GetLastModificationTime: TDateTime;
|
||||
end;
|
||||
|
||||
IRecordStreamReader<T: record> = interface
|
||||
['{C1B7E8F0-5A0A-4F3B-BD28-18D5E5A3F7A0}']
|
||||
function GetCurrentRecord: T;
|
||||
function MoveToNextRecord: Boolean;
|
||||
procedure Reset;
|
||||
function GetCurrentRecordIndex: Int64;
|
||||
function Seek(RecordIndex: Int64): Int64;
|
||||
|
||||
property CurrentRecord: T read GetCurrentRecord;
|
||||
property CurrentRecordIndex: Int64 read GetCurrentRecordIndex;
|
||||
end;
|
||||
|
||||
TLocalFilePage = class(TInterfacedObject, IDataPage)
|
||||
private
|
||||
FActualFilePath: string;
|
||||
FMemoryStream: TMemoryStream;
|
||||
FLastModifiedOnDisk: TDateTime;
|
||||
|
||||
function GetStream: TStream;
|
||||
function GetSourceIdentifier: string;
|
||||
function GetLastModificationTime: TDateTime;
|
||||
public
|
||||
constructor Create(const AActualFilePath: string; ADataStream: TMemoryStream; ALastModifiedTimeOnDisk: TDateTime);
|
||||
destructor Destroy; override;
|
||||
class function IsFileModified(const AFileName: string; KnownLastModified: TDateTime): Boolean;
|
||||
end;
|
||||
|
||||
TStreamingFileRecordReader<T: record> = class(TInterfacedObject, IRecordStreamReader<T>)
|
||||
private
|
||||
FDirectoryPath: string;
|
||||
FDataSetName: string;
|
||||
FExtension: string;
|
||||
|
||||
FFilePaths: TStringList;
|
||||
FCachedPages: TObjectDictionary<string, IDataPage>;
|
||||
FPageCacheQueue: TQueue<string>;
|
||||
FMaxCacheSize: Integer;
|
||||
FPreloadPageTask: ITask;
|
||||
FLockObject: TObject;
|
||||
|
||||
FCurrentPage: IDataPage;
|
||||
FCurrentPageStream: TStream;
|
||||
FCurrentPageIndexInFileList: Integer;
|
||||
|
||||
FRecordSize: Integer;
|
||||
FCurrentRecord: T;
|
||||
FRecordsInCurrentPage: Int64;
|
||||
FRecordsReadFromCurrentPage: Int64;
|
||||
FGlobalRecordIndex: Int64;
|
||||
|
||||
procedure ScanDirectoryAndMatchFiles;
|
||||
function LoadPage(ActualFilePath: string): IDataPage;
|
||||
procedure EnsurePageInCache(ActualFilePath: string; IsPreload: Boolean = False);
|
||||
procedure EvictOldestPage;
|
||||
procedure DoPreloadWork(const APathToPreload: string);
|
||||
procedure StartPreloadNextPage;
|
||||
procedure InvalidatePageIfModified(PageToCheck: IDataPage; IsCurrentPage: Boolean);
|
||||
function ActivatePage(PageIndexInFileList: Integer): Boolean;
|
||||
function GetRecordCountForFile(ActualFilePath: string): Int64;
|
||||
|
||||
function GetCurrentRecord: T;
|
||||
function MoveToNextRecord: Boolean;
|
||||
procedure Reset;
|
||||
function GetCurrentRecordIndex: Int64;
|
||||
function Seek(RecordIndex: Int64): Int64;
|
||||
public
|
||||
constructor Create(const ADirectoryPath: string; const ADataSetName: string; const AExtension: string; AMaxCacheSize: Integer = 2);
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.DateUtils, System.Types, System.StrUtils,
|
||||
System.Math;
|
||||
|
||||
{ TLocalFilePage }
|
||||
|
||||
constructor TLocalFilePage.Create(const AActualFilePath: string; ADataStream: TMemoryStream; ALastModifiedTimeOnDisk: TDateTime);
|
||||
begin
|
||||
inherited Create;
|
||||
FActualFilePath := AActualFilePath;
|
||||
FMemoryStream := ADataStream;
|
||||
FMemoryStream.Position := 0;
|
||||
FLastModifiedOnDisk := ALastModifiedTimeOnDisk;
|
||||
end;
|
||||
|
||||
destructor TLocalFilePage.Destroy;
|
||||
begin
|
||||
FMemoryStream.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
function TLocalFilePage.GetStream: TStream;
|
||||
begin
|
||||
FMemoryStream.Position := 0;
|
||||
result := FMemoryStream;
|
||||
end;
|
||||
|
||||
function TLocalFilePage.GetSourceIdentifier: string;
|
||||
begin
|
||||
result := FActualFilePath;
|
||||
end;
|
||||
|
||||
function TLocalFilePage.GetLastModificationTime: TDateTime;
|
||||
begin
|
||||
result := FLastModifiedOnDisk;
|
||||
end;
|
||||
|
||||
class function TLocalFilePage.IsFileModified(const AFileName: string; KnownLastModified: TDateTime): Boolean;
|
||||
var
|
||||
currentModTime: TDateTime;
|
||||
begin
|
||||
if TFile.Exists(AFileName) then
|
||||
begin
|
||||
currentModTime := TFile.GetLastWriteTime(AFileName);
|
||||
result := Abs(MilliSecondsBetween(currentModTime, KnownLastModified)) > 1000;
|
||||
end
|
||||
else
|
||||
begin
|
||||
result := True;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TStreamingFileRecordReader<T> }
|
||||
|
||||
constructor TStreamingFileRecordReader<T>.Create(const ADirectoryPath: string; const ADataSetName: string; const AExtension: string; AMaxCacheSize: Integer);
|
||||
begin
|
||||
inherited Create;
|
||||
FLockObject := TObject.Create;
|
||||
|
||||
if not TDirectory.Exists(ADirectoryPath) then
|
||||
raise EDirectoryNotFoundException.Create('Directory not found: ' + ADirectoryPath);
|
||||
if Trim(ADataSetName) = '' then
|
||||
raise EArgumentException.Create('DataSetName cannot be empty.');
|
||||
if Trim(AExtension) = '' then
|
||||
raise EArgumentException.Create('Extension cannot be empty.');
|
||||
|
||||
FDirectoryPath := IncludeTrailingPathDelimiter(ADirectoryPath);
|
||||
FDataSetName := ADataSetName;
|
||||
FExtension := AExtension;
|
||||
if StartsText('.', FExtension) then
|
||||
FExtension := Copy(FExtension, 2, Length(FExtension) -1);
|
||||
|
||||
FFilePaths := TStringList.Create;
|
||||
FFilePaths.Sorted := True;
|
||||
|
||||
FCachedPages := TObjectDictionary<string, IDataPage>.Create([doOwnsValues]);
|
||||
FPageCacheQueue := TQueue<string>.Create;
|
||||
FMaxCacheSize := System.Math.Max(2, AMaxCacheSize);
|
||||
FRecordSize := SizeOf(T);
|
||||
if FRecordSize = 0 then
|
||||
raise EArgumentException.Create('Record size for type T cannot be zero.');
|
||||
|
||||
FCurrentPageIndexInFileList := -1;
|
||||
FGlobalRecordIndex := -1;
|
||||
FRecordsReadFromCurrentPage := 0;
|
||||
FRecordsInCurrentPage := 0;
|
||||
FCurrentPage := nil;
|
||||
FCurrentPageStream := nil;
|
||||
|
||||
ScanDirectoryAndMatchFiles;
|
||||
Reset;
|
||||
end;
|
||||
|
||||
destructor TStreamingFileRecordReader<T>.Destroy;
|
||||
var
|
||||
taskToWaitFor: ITask;
|
||||
begin
|
||||
taskToWaitFor := nil;
|
||||
TMonitor.Enter(FLockObject);
|
||||
try
|
||||
taskToWaitFor := FPreloadPageTask;
|
||||
FPreloadPageTask := nil;
|
||||
finally
|
||||
TMonitor.Exit(FLockObject);
|
||||
end;
|
||||
|
||||
if (taskToWaitFor <> nil) and (taskToWaitFor.Status in [TTaskStatus.Created, TTaskStatus.WaitingToRun, TTaskStatus.Running]) then
|
||||
begin
|
||||
// Consider taskToWaitFor.Cancel; taskToWaitFor.Wait;
|
||||
end;
|
||||
|
||||
FFilePaths.Free;
|
||||
FCachedPages.Free;
|
||||
FPageCacheQueue.Free;
|
||||
FLockObject.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
procedure TStreamingFileRecordReader<T>.ScanDirectoryAndMatchFiles;
|
||||
var
|
||||
allFiles: TStringDynArray;
|
||||
fileNameOnly, filePath: string;
|
||||
expectedPrefix, baseExtensionPattern, zipExtensionPattern: string;
|
||||
begin
|
||||
TMonitor.Enter(FLockObject);
|
||||
try
|
||||
FFilePaths.Clear;
|
||||
allFiles := TDirectory.GetFiles(FDirectoryPath);
|
||||
expectedPrefix := FDataSetName + '_';
|
||||
baseExtensionPattern := '.' + FExtension;
|
||||
zipExtensionPattern := baseExtensionPattern + '_zip';
|
||||
|
||||
for filePath in allFiles do
|
||||
begin
|
||||
fileNameOnly := ExtractFileName(filePath);
|
||||
if StartsText(expectedPrefix, fileNameOnly) then
|
||||
begin
|
||||
if EndsText(zipExtensionPattern, fileNameOnly.ToLower) or EndsText(baseExtensionPattern, fileNameOnly.ToLower) then
|
||||
begin
|
||||
FFilePaths.Add(filePath);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
TMonitor.Exit(FLockObject);
|
||||
end;
|
||||
end;
|
||||
|
||||
function TStreamingFileRecordReader<T>.LoadPage(ActualFilePath: string): IDataPage;
|
||||
var
|
||||
dataStream: TMemoryStream;
|
||||
fileLastModified: TDateTime;
|
||||
zipFile: TZipFile;
|
||||
isCompressed: Boolean;
|
||||
fileExtensionOnDisk: string;
|
||||
decompressionStream: TStream;
|
||||
localHeader: TZipHeader;
|
||||
fileNameInZip: string;
|
||||
begin
|
||||
dataStream := TMemoryStream.Create;
|
||||
try
|
||||
fileLastModified := TFile.GetLastWriteTime(ActualFilePath);
|
||||
fileExtensionOnDisk := ExtractFileExt(ActualFilePath);
|
||||
isCompressed := EndsText('_zip', fileExtensionOnDisk.ToLower);
|
||||
|
||||
if isCompressed then
|
||||
begin
|
||||
zipFile := TZipFile.Create;
|
||||
try
|
||||
zipFile.Open(ActualFilePath, zmRead);
|
||||
if zipFile.FileCount > 0 then
|
||||
begin
|
||||
fileNameInZip := zipFile.FileNames[0];
|
||||
zipFile.Read(fileNameInZip, decompressionStream, localHeader);
|
||||
try
|
||||
dataStream.CopyFrom(decompressionStream, 0);
|
||||
finally
|
||||
decompressionStream.Free;
|
||||
end;
|
||||
end
|
||||
else
|
||||
raise Exception.Create('Compressed file is empty: ' + ActualFilePath);
|
||||
finally
|
||||
zipFile.Free;
|
||||
end;
|
||||
end
|
||||
else
|
||||
dataStream.LoadFromFile(ActualFilePath);
|
||||
|
||||
dataStream.Position := 0;
|
||||
result := TLocalFilePage.Create(ActualFilePath, dataStream, fileLastModified);
|
||||
except
|
||||
dataStream.Free;
|
||||
raise;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TStreamingFileRecordReader<T>.EnsurePageInCache(ActualFilePath: string; IsPreload: Boolean);
|
||||
var
|
||||
page: IDataPage;
|
||||
newlyLoadedPage: IDataPage;
|
||||
begin
|
||||
// Assumes FLockObject is held by the caller
|
||||
if not FCachedPages.TryGetValue(ActualFilePath, page) then
|
||||
begin
|
||||
if FCachedPages.Count >= FMaxCacheSize then
|
||||
EvictOldestPage;
|
||||
newlyLoadedPage := LoadPage(ActualFilePath);
|
||||
FCachedPages.Add(ActualFilePath, newlyLoadedPage);
|
||||
FPageCacheQueue.Enqueue(ActualFilePath);
|
||||
end
|
||||
else
|
||||
begin
|
||||
if not IsPreload then
|
||||
InvalidatePageIfModified(page, page = FCurrentPage);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TStreamingFileRecordReader<T>.EvictOldestPage;
|
||||
var
|
||||
oldestFilePath: string;
|
||||
begin
|
||||
// Assumes FLockObject is held by the caller
|
||||
if FPageCacheQueue.Count > 0 then
|
||||
begin
|
||||
oldestFilePath := FPageCacheQueue.Dequeue;
|
||||
if FCachedPages.ContainsKey(oldestFilePath) then
|
||||
FCachedPages.Remove(oldestFilePath);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TStreamingFileRecordReader<T>.InvalidatePageIfModified(PageToCheck: IDataPage; IsCurrentPage: Boolean);
|
||||
var
|
||||
actualFilePath: string;
|
||||
reloadedPage: IDataPage;
|
||||
begin
|
||||
// Assumes FLockObject is held by the caller
|
||||
if PageToCheck = nil then exit;
|
||||
|
||||
actualFilePath := PageToCheck.GetSourceIdentifier;
|
||||
|
||||
if TLocalFilePage.IsFileModified(actualFilePath, PageToCheck.GetLastModificationTime) then
|
||||
begin
|
||||
FCachedPages.Remove(actualFilePath);
|
||||
|
||||
if IsCurrentPage then
|
||||
begin
|
||||
FCurrentPage := nil;
|
||||
FCurrentPageStream := nil;
|
||||
|
||||
reloadedPage := LoadPage(actualFilePath);
|
||||
FCachedPages.Add(actualFilePath, reloadedPage);
|
||||
|
||||
FCurrentPage := reloadedPage;
|
||||
FCurrentPageStream := FCurrentPage.GetStream;
|
||||
if (FCurrentPageStream <> nil) and (FCurrentPageStream.Size >= FRecordSize) then
|
||||
FRecordsInCurrentPage := FCurrentPageStream.Size div FRecordSize
|
||||
else
|
||||
FRecordsInCurrentPage := 0;
|
||||
|
||||
if (FRecordsReadFromCurrentPage * FRecordSize < FCurrentPageStream.Size) and (FRecordsReadFromCurrentPage <= FRecordsInCurrentPage) then
|
||||
FCurrentPageStream.Position := FRecordsReadFromCurrentPage * FRecordSize
|
||||
else
|
||||
begin
|
||||
FCurrentPageStream.Position := 0;
|
||||
FRecordsReadFromCurrentPage := 0;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TStreamingFileRecordReader<T>.DoPreloadWork(const APathToPreload: string);
|
||||
begin
|
||||
try
|
||||
TMonitor.Enter(FLockObject);
|
||||
try
|
||||
if not FCachedPages.ContainsKey(APathToPreload) then
|
||||
EnsurePageInCache(APathToPreload, True);
|
||||
finally
|
||||
TMonitor.Exit(FLockObject);
|
||||
end;
|
||||
except
|
||||
on E: Exception do
|
||||
begin
|
||||
// Optional: Log error E.Message
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TStreamingFileRecordReader<T>.StartPreloadNextPage;
|
||||
var
|
||||
nextPageIndexToLoad: Integer;
|
||||
nextFilePath: string;
|
||||
currentTask: ITask;
|
||||
begin
|
||||
TMonitor.Enter(FLockObject);
|
||||
try
|
||||
currentTask := FPreloadPageTask;
|
||||
if (currentTask <> nil) and (currentTask.Status in [TTaskStatus.Created, TTaskStatus.WaitingToRun, TTaskStatus.Running]) then
|
||||
exit;
|
||||
|
||||
nextPageIndexToLoad := FCurrentPageIndexInFileList + 1;
|
||||
if nextPageIndexToLoad < FFilePaths.Count then
|
||||
begin
|
||||
nextFilePath := FFilePaths[nextPageIndexToLoad];
|
||||
if FCachedPages.ContainsKey(nextFilePath) then
|
||||
exit;
|
||||
|
||||
FPreloadPageTask := TTask.Run(
|
||||
procedure
|
||||
begin
|
||||
DoPreloadWork(nextFilePath);
|
||||
end
|
||||
);
|
||||
end;
|
||||
finally
|
||||
TMonitor.Exit(FLockObject);
|
||||
end;
|
||||
end;
|
||||
|
||||
function TStreamingFileRecordReader<T>.ActivatePage(PageIndexInFileList: Integer): Boolean;
|
||||
var
|
||||
actualFilePath: string;
|
||||
pageFromCache: IDataPage;
|
||||
begin
|
||||
result := False;
|
||||
if (PageIndexInFileList < 0) or (PageIndexInFileList >= FFilePaths.Count) then
|
||||
begin FCurrentPage := nil; FCurrentPageStream := nil; exit; end;
|
||||
|
||||
actualFilePath := FFilePaths[PageIndexInFileList];
|
||||
|
||||
TMonitor.Enter(FLockObject);
|
||||
try
|
||||
EnsurePageInCache(actualFilePath, False);
|
||||
if not FCachedPages.TryGetValue(actualFilePath, pageFromCache) then
|
||||
begin FCurrentPage := nil; FCurrentPageStream := nil; exit; end;
|
||||
|
||||
FCurrentPage := pageFromCache;
|
||||
FCurrentPageStream := FCurrentPage.GetStream;
|
||||
FCurrentPageIndexInFileList := PageIndexInFileList;
|
||||
FRecordsReadFromCurrentPage := 0;
|
||||
if (FCurrentPageStream <> nil) and (FCurrentPageStream.Size >= FRecordSize) then
|
||||
FRecordsInCurrentPage := FCurrentPageStream.Size div FRecordSize
|
||||
else
|
||||
FRecordsInCurrentPage := 0;
|
||||
|
||||
if FRecordsInCurrentPage > 0 then
|
||||
begin
|
||||
result := True;
|
||||
StartPreloadNextPage;
|
||||
end;
|
||||
finally
|
||||
TMonitor.Exit(FLockObject);
|
||||
end;
|
||||
end;
|
||||
|
||||
function TStreamingFileRecordReader<T>.GetRecordCountForFile(ActualFilePath: string): Int64;
|
||||
var
|
||||
page: IDataPage;
|
||||
stream: TStream;
|
||||
begin
|
||||
result := 0;
|
||||
EnsurePageInCache(ActualFilePath, False); // Assumes FLockObject is held by caller
|
||||
if FCachedPages.TryGetValue(ActualFilePath, page) then
|
||||
begin
|
||||
stream := page.GetStream;
|
||||
if (stream <> nil) and (stream.Size >= FRecordSize) then
|
||||
result := stream.Size div FRecordSize;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TStreamingFileRecordReader<T>.GetCurrentRecord: T;
|
||||
begin
|
||||
if FGlobalRecordIndex < 0 then
|
||||
Result := System.Default(T)
|
||||
else
|
||||
Result := FCurrentRecord;
|
||||
end;
|
||||
|
||||
function TStreamingFileRecordReader<T>.GetCurrentRecordIndex: Int64;
|
||||
begin
|
||||
result := FGlobalRecordIndex;
|
||||
end;
|
||||
|
||||
function TStreamingFileRecordReader<T>.MoveToNextRecord: Boolean;
|
||||
var
|
||||
bytesRead: NativeInt; // ReadData<T> returns NativeInt
|
||||
attemptedNextPageActivation: Boolean;
|
||||
begin
|
||||
result := False;
|
||||
attemptedNextPageActivation := False;
|
||||
|
||||
TMonitor.Enter(FLockObject);
|
||||
try
|
||||
while True do
|
||||
begin
|
||||
if FCurrentPageStream = nil then
|
||||
begin
|
||||
if attemptedNextPageActivation or (FCurrentPageIndexInFileList >= FFilePaths.Count - 1) then
|
||||
begin FGlobalRecordIndex := -1; exit; end;
|
||||
if not ActivatePage(FCurrentPageIndexInFileList + 1) then
|
||||
begin FGlobalRecordIndex := -1; exit; end;
|
||||
attemptedNextPageActivation := True;
|
||||
end;
|
||||
|
||||
if FRecordsReadFromCurrentPage >= FRecordsInCurrentPage then
|
||||
begin
|
||||
if attemptedNextPageActivation or (FCurrentPageIndexInFileList >= FFilePaths.Count - 1) then
|
||||
begin FGlobalRecordIndex := -1; exit; end;
|
||||
FCurrentPageStream := nil;
|
||||
if not ActivatePage(FCurrentPageIndexInFileList + 1) then
|
||||
begin FGlobalRecordIndex := -1; exit; end;
|
||||
attemptedNextPageActivation := True;
|
||||
continue;
|
||||
end;
|
||||
|
||||
FCurrentPageStream.Position := FRecordsReadFromCurrentPage * FRecordSize;
|
||||
if (FCurrentPageStream.Size - FCurrentPageStream.Position) < FRecordSize then
|
||||
begin
|
||||
FRecordsReadFromCurrentPage := FRecordsInCurrentPage;
|
||||
attemptedNextPageActivation := False;
|
||||
FCurrentPageStream := nil;
|
||||
continue;
|
||||
end;
|
||||
|
||||
// Verwende die generische TStream.ReadData<T> Methode
|
||||
bytesRead := FCurrentPageStream.ReadData<T>(FCurrentRecord); // User line 518 context
|
||||
|
||||
if bytesRead = FRecordSize then // FRecordSize is SizeOf(T)
|
||||
begin
|
||||
Inc(FRecordsReadFromCurrentPage);
|
||||
if FGlobalRecordIndex < 0 then
|
||||
FGlobalRecordIndex := 0
|
||||
else
|
||||
Inc(FGlobalRecordIndex); // << User line 518 (E2010), hopefully fixed by ReadData<T>
|
||||
result := True; exit;
|
||||
end
|
||||
else
|
||||
begin
|
||||
FRecordsReadFromCurrentPage := FRecordsInCurrentPage;
|
||||
attemptedNextPageActivation := False;
|
||||
FCurrentPageStream := nil;
|
||||
continue;
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
TMonitor.Exit(FLockObject);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TStreamingFileRecordReader<T>.Reset;
|
||||
var
|
||||
taskToCheck: ITask;
|
||||
begin
|
||||
TMonitor.Enter(FLockObject);
|
||||
try
|
||||
taskToCheck := FPreloadPageTask;
|
||||
if (taskToCheck <> nil) and (taskToCheck.Status in [TTaskStatus.Created, TTaskStatus.WaitingToRun, TTaskStatus.Running]) then
|
||||
begin
|
||||
// Cancellation logic
|
||||
end;
|
||||
FPreloadPageTask := nil;
|
||||
|
||||
FCurrentPageIndexInFileList := -1;
|
||||
FGlobalRecordIndex := -1;
|
||||
FRecordsReadFromCurrentPage := 0;
|
||||
FRecordsInCurrentPage := 0;
|
||||
FCurrentPage := nil;
|
||||
FCurrentPageStream := nil;
|
||||
FCurrentRecord := System.Default(T);
|
||||
if FFilePaths.Count > 0 then
|
||||
if not ActivatePage(0) then;
|
||||
finally
|
||||
TMonitor.Exit(FLockObject);
|
||||
end;
|
||||
end;
|
||||
|
||||
function TStreamingFileRecordReader<T>.Seek(RecordIndex: Int64): Int64;
|
||||
var
|
||||
pageIdx: Integer;
|
||||
cumulativeRecordOffset: Int64;
|
||||
recordsInCurrentFile: Int64;
|
||||
recordOffsetInPage: Int64;
|
||||
effectiveTargetIdx: Int64;
|
||||
i: Integer;
|
||||
sumRecordsBeforeThisPage: Int64;
|
||||
bytesRead: NativeInt; // For ReadData<T>
|
||||
begin
|
||||
TMonitor.Enter(FLockObject);
|
||||
try
|
||||
if FFilePaths.Count = 0 then
|
||||
begin
|
||||
FCurrentPageIndexInFileList := -1; FGlobalRecordIndex := -1; FCurrentPage := nil; FCurrentPageStream := nil;
|
||||
FCurrentRecord := System.Default(T);
|
||||
result := -1; exit;
|
||||
end;
|
||||
|
||||
effectiveTargetIdx := RecordIndex;
|
||||
if effectiveTargetIdx < 0 then effectiveTargetIdx := 0;
|
||||
|
||||
cumulativeRecordOffset := 0;
|
||||
for pageIdx := 0 to FFilePaths.Count - 1 do
|
||||
begin
|
||||
recordsInCurrentFile := GetRecordCountForFile(FFilePaths[pageIdx]);
|
||||
if effectiveTargetIdx < cumulativeRecordOffset + recordsInCurrentFile then
|
||||
begin
|
||||
if not ActivatePage(pageIdx) then
|
||||
begin
|
||||
FCurrentPageIndexInFileList := -1; FGlobalRecordIndex := -1; FCurrentPage := nil; FCurrentPageStream := nil;
|
||||
FCurrentRecord := System.Default(T);
|
||||
result := -1; exit;
|
||||
end;
|
||||
recordOffsetInPage := effectiveTargetIdx - cumulativeRecordOffset;
|
||||
FCurrentPageStream.Position := recordOffsetInPage * FRecordSize;
|
||||
|
||||
// Verwende die generische TStream.ReadData<T> Methode
|
||||
bytesRead := FCurrentPageStream.ReadData<T>(FCurrentRecord);
|
||||
|
||||
if bytesRead = FRecordSize then
|
||||
begin
|
||||
FGlobalRecordIndex := effectiveTargetIdx; FRecordsReadFromCurrentPage := recordOffsetInPage + 1;
|
||||
result := FGlobalRecordIndex; exit;
|
||||
end
|
||||
else
|
||||
begin // User line ~604 context (E2008)
|
||||
FCurrentPageIndexInFileList := -1; FGlobalRecordIndex := -1; FCurrentPage := nil; FCurrentPageStream := nil;
|
||||
FCurrentRecord := System.Default(T);
|
||||
result := -1; exit;
|
||||
end;
|
||||
end;
|
||||
inc(cumulativeRecordOffset, recordsInCurrentFile);
|
||||
end;
|
||||
|
||||
if cumulativeRecordOffset = 0 then // User line ~616 context (E2029) for assignment after this block
|
||||
begin
|
||||
FCurrentPageIndexInFileList := -1; FGlobalRecordIndex := -1; FCurrentPage := nil; FCurrentPageStream := nil;
|
||||
FCurrentRecord := System.Default(T);
|
||||
result := -1; exit;
|
||||
end;
|
||||
|
||||
for pageIdx := FFilePaths.Count - 1 downto 0 do
|
||||
begin
|
||||
recordsInCurrentFile := GetRecordCountForFile(FFilePaths[pageIdx]);
|
||||
if recordsInCurrentFile > 0 then
|
||||
begin
|
||||
if not ActivatePage(pageIdx) then
|
||||
begin
|
||||
FCurrentPageIndexInFileList := -1; FGlobalRecordIndex := -1; FCurrentPage := nil; FCurrentPageStream := nil;
|
||||
FCurrentRecord := System.Default(T);
|
||||
result := -1; exit;
|
||||
end;
|
||||
recordOffsetInPage := recordsInCurrentFile - 1;
|
||||
FCurrentPageStream.Position := recordOffsetInPage * FRecordSize;
|
||||
|
||||
// Verwende die generische TStream.ReadData<T> Methode
|
||||
bytesRead := FCurrentPageStream.ReadData<T>(FCurrentRecord);
|
||||
|
||||
if bytesRead = FRecordSize then
|
||||
begin
|
||||
sumRecordsBeforeThisPage := 0;
|
||||
for i := 0 to pageIdx - 1 do
|
||||
sumRecordsBeforeThisPage := sumRecordsBeforeThisPage + GetRecordCountForFile(FFilePaths[i]);
|
||||
FGlobalRecordIndex := sumRecordsBeforeThisPage + recordOffsetInPage; // User line 616 context (E2029/E2014)
|
||||
FRecordsReadFromCurrentPage := recordOffsetInPage + 1;
|
||||
result := FGlobalRecordIndex; exit;
|
||||
end
|
||||
else
|
||||
begin // User line ~639 context (E2008)
|
||||
FCurrentPageIndexInFileList := -1; FGlobalRecordIndex := -1; FCurrentPage := nil; FCurrentPageStream := nil;
|
||||
FCurrentRecord := System.Default(T);
|
||||
result := -1; exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
FCurrentPageIndexInFileList := -1; FGlobalRecordIndex := -1; FCurrentPage := nil; FCurrentPageStream := nil;
|
||||
FCurrentRecord := System.Default(T);
|
||||
result := -1; exit;
|
||||
finally
|
||||
TMonitor.Exit(FLockObject);
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
Reference in New Issue
Block a user