mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-07 08:07:56 +02:00
534 lines
14 KiB
ObjectPascal
534 lines
14 KiB
ObjectPascal
unit webfilecache;
|
|
|
|
{$mode objfpc}
|
|
|
|
// Enable this to write lots of debugging info to the browser console.
|
|
{$DEFINE VERBOSEWEBCACHE}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, JS, Web, fpjson, pas2jsfs, pscanner, contnrs;
|
|
|
|
type
|
|
TPas2jsWebFS = Class;
|
|
|
|
{ TWebFileContent }
|
|
|
|
TWebFileContent = Class(TObject)
|
|
private
|
|
FContents: string;
|
|
FFileName: String;
|
|
FModified: Boolean;
|
|
procedure SetContents(AValue: string);
|
|
Public
|
|
Constructor Create(const aFileName,aContents : String);
|
|
Property FileName : String Read FFileName Write FFileName;
|
|
Property Contents : string Read FContents Write SetContents;
|
|
Property Modified : Boolean Read FModified;
|
|
end;
|
|
{ TWebFilesCache }
|
|
|
|
TWebFilesCache = Class(TObject)
|
|
Private
|
|
FFiles : TFPObjectHashTable;
|
|
Function FindFile(aFileName : String) : TWebFileContent;
|
|
Public
|
|
Constructor Create;
|
|
Destructor Destroy; override;
|
|
Function HasFile(aFileName : String) : Boolean;
|
|
Function GetFileContent(Const aFileName : String) : String;
|
|
function SetFileContent(const aFileName, aContent: String): Boolean;
|
|
end;
|
|
|
|
{ TPas2jsWebFile }
|
|
|
|
TPas2jsWebFile = Class(TPas2jsFile)
|
|
public
|
|
function CreateLineReader(RaiseOnError: boolean): TSourceLineReader; override;
|
|
function Load(RaiseOnError: boolean; Binary: boolean): boolean; override;
|
|
end;
|
|
|
|
{ TWebSourceLineReader }
|
|
|
|
TWebSourceLineReader = Class(TSourceLineReader)
|
|
private
|
|
FFS: TPas2jsFS;
|
|
Protected
|
|
Property FS : TPas2jsFS Read FFS;
|
|
Procedure IncLineNumber; override;
|
|
end;
|
|
|
|
// aFileName is the original filename, not normalized one
|
|
TLoadFileEvent = Reference to Procedure(Sender : TObject; aFileName : String; aError : string);
|
|
|
|
{ TLoadFileRequest }
|
|
|
|
TLoadFileRequest = Class(TObject)
|
|
FFS : TPas2jsWebFS;
|
|
FFileName : string;
|
|
FXML : TJSXMLHttpRequest;
|
|
FOnLoaded : TLoadFileEvent;
|
|
private
|
|
procedure DoChange;
|
|
Public
|
|
constructor Create(aFS: TPas2jsWebFS; const aFileName : string; aOnLoaded: TLoadFileEvent);
|
|
Procedure DoLoad(const aURL : String);
|
|
end;
|
|
|
|
|
|
{ TPas2jsWebFS }
|
|
|
|
TPas2jsWebFS = Class(TPas2jsFS)
|
|
Private
|
|
FCache : TWebFilesCache;
|
|
FLoadBaseURL: String;
|
|
FOnLoadedFile: TLoadFileEvent;
|
|
protected
|
|
// Only for names, no paths
|
|
Class Function NormalizeFileName(Const aFileName : String) : String;
|
|
function FindSourceFileName(const aFilename: string): String; override;
|
|
public
|
|
Constructor Create; override;
|
|
// Overrides
|
|
function CreateResolver: TPas2jsFSResolver; override;
|
|
function FileExists(const aFileName: String): Boolean; override;
|
|
function FindCustomJSFileName(const aFilename: string): String; override;
|
|
function FindIncludeFileName(const aFilename, SrcDir, ModuleDir: string; Mode: TModeSwitch): String; override;
|
|
function FindUnitFileName(const aUnitname, InFilename, ModuleDir: string; out IsForeign: boolean): String; override;
|
|
function FindUnitJSFileName(const aUnitFilename: string): String; override;
|
|
function LoadFile(Filename: string; Binary: boolean=false): TPas2jsFile; override;
|
|
procedure SaveToFile(ms: TFPJSStream; Filename: string); override;
|
|
Function SetFileContent(Const aFileName,aContents : String) : Boolean;
|
|
Function GetFileContent(Const aFileName : String) : String;
|
|
// Returns false if the file was already loaded. OnLoaded is called in either case.
|
|
Function LoadFile(aFileName : String; OnLoaded : TLoadFileEvent = Nil) : Boolean;
|
|
// Returns number of load requests. OnLoaded is called for each file in the list
|
|
Function LoadFiles(aList : TStrings;OnLoaded : TLoadFileEvent = Nil) : Integer;
|
|
Function LoadFiles(aList : array of String;OnLoaded : TLoadFileEvent = Nil) : integer;
|
|
Property OnLoadedFile : TLoadFileEvent Read FOnLoadedFile Write FOnLoadedFile;
|
|
Property LoadBaseURL : String Read FLoadBaseURL Write FLoadBaseURL;
|
|
end;
|
|
|
|
{ TPas2jsFileResolver }
|
|
|
|
{ TPas2jsWebResolver }
|
|
|
|
TPas2jsWebResolver = class(TPas2jsFSResolver)
|
|
private
|
|
function GetWebFS: TPas2jsWebFS;
|
|
public
|
|
Property WebFS : TPas2jsWebFS Read GetWebFS;
|
|
end;
|
|
|
|
implementation
|
|
|
|
{ TWebSourceLineReader }
|
|
|
|
procedure TWebSourceLineReader.IncLineNumber;
|
|
begin
|
|
if (FFS<>nil) then
|
|
FFS.IncReadLineCounter;
|
|
inherited IncLineNumber;
|
|
end;
|
|
|
|
{ TLoadFileRequest }
|
|
|
|
procedure TLoadFileRequest.DoChange;
|
|
|
|
Var
|
|
Err : String;
|
|
begin
|
|
Case FXML.readyState of
|
|
TJSXMLHttpRequest.UNSENT : ;
|
|
TJSXMLHttpRequest.OPENED : ;
|
|
TJSXMLHttpRequest.HEADERS_RECEIVED : ;
|
|
TJSXMLHttpRequest.LOADING : ;
|
|
TJSXMLHttpRequest.DONE :
|
|
begin
|
|
if (FXML.Status div 100)=2 then
|
|
begin
|
|
Err:='';
|
|
// FS will normalize filename
|
|
FFS.SetFileContent(FFileName,FXML.responsetext)
|
|
end
|
|
else
|
|
Err:='Error loading file: '+FXML.StatusText;
|
|
If Assigned(FOnLoaded) then
|
|
FOnLoaded(FFS,FFileName,Err);
|
|
if Assigned(FFS.OnLoadedFile) then
|
|
FFS.OnLoadedFile(FFS,FFileName,Err);
|
|
Free;
|
|
end;
|
|
end
|
|
end;
|
|
|
|
constructor TLoadFileRequest.Create(aFS: TPas2jsWebFS; const aFileName : string; aOnLoaded: TLoadFileEvent);
|
|
begin
|
|
FFS:=aFS;
|
|
FOnLoaded:=aOnLoaded;
|
|
FFileName:=aFileName;
|
|
end;
|
|
|
|
Procedure TLoadFileRequest.DoLoad(const aURL: String);
|
|
begin
|
|
FXML:=TJSXMLHttpRequest.new;
|
|
FXML.onreadystatechange:=@DoChange;
|
|
// Maybe one day allow do this sync, so the compiler can load files on demand.
|
|
FXML.Open('GET',aURL);
|
|
FXML.Send;
|
|
end;
|
|
|
|
{ TPas2jsWebFile }
|
|
|
|
function TPas2jsWebFile.CreateLineReader(RaiseOnError: boolean): TSourceLineReader;
|
|
begin
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': Creating line reader for ',FileName);
|
|
{$ENDIF}
|
|
if Load(RaiseOnError,False) then
|
|
begin
|
|
Result:=TWebSourceLineReader.Create(FileName,Source);
|
|
TWebSourceLineReader(Result).FFS:=Self.FS;
|
|
end
|
|
else
|
|
Result:=Nil;
|
|
end;
|
|
|
|
function TPas2jsWebFile.Load(RaiseOnError: boolean; Binary: boolean): boolean;
|
|
begin
|
|
Result:=False;
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': Loading for ',FileName);
|
|
{$ENDIF}
|
|
With (FS as TPas2jsWebFS).FCache do
|
|
if HasFile(FileName) then
|
|
begin
|
|
SetSource(GetFileContent(FileName));
|
|
Result:=True;
|
|
end;
|
|
if Not Result then
|
|
if RaiseOnError then
|
|
Raise EFileNotFoundError.Create('File not loaded '+FileName)
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
else Writeln('File not loaded '+FileName);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{ TWebFilesCache }
|
|
|
|
function TWebFilesCache.FindFile(aFileName: String): TWebFileContent;
|
|
|
|
Var
|
|
N : THTCustomNode;
|
|
|
|
begin
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': Looking for file : ',aFileName);
|
|
{$ENDIF}
|
|
N:=FFiles.Find(aFileName);
|
|
if N=Nil then
|
|
result:=Nil
|
|
else
|
|
Result:=TWebFileContent(THTObjectNode(N).Data);
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': Looking for file : ',aFileName, ': ',Assigned(Result));
|
|
{$ENDIF}
|
|
end;
|
|
|
|
constructor TWebFilesCache.Create;
|
|
begin
|
|
FFiles:=TFPObjectHashTable.Create(True);
|
|
end;
|
|
|
|
destructor TWebFilesCache.Destroy;
|
|
begin
|
|
FreeAndNil(FFiles);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TWebFilesCache.HasFile(aFileName: String): Boolean;
|
|
begin
|
|
Result:=FindFile(aFileName)<>Nil;
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': HasFile(',aFileName,') : ',Result);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function TWebFilesCache.GetFileContent(const aFileName: String): String;
|
|
|
|
Var
|
|
W : TWebFileContent;
|
|
|
|
begin
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': GetFileContent(',aFileName,')');
|
|
{$ENDIF}
|
|
W:=FindFile(aFileName);
|
|
if Assigned(W) then
|
|
Result:=W.Contents
|
|
else
|
|
Raise EFileNotFoundError.Create('No such file '+AFileName);
|
|
end;
|
|
|
|
function TWebFilesCache.SetFileContent(const aFileName, aContent: String) : Boolean;
|
|
|
|
Var
|
|
W : TWebFileContent;
|
|
|
|
begin
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': SetFileContent(',aFileName,')');
|
|
{$ENDIF}
|
|
W:=FindFile(aFileName);
|
|
Result:=Assigned(W);
|
|
if Result then
|
|
W.Contents:=aContent
|
|
else
|
|
FFiles.Add(aFileName,TWebFileContent.Create(aFileName,aContent));
|
|
end;
|
|
|
|
{ TWebFileContent }
|
|
|
|
procedure TWebFileContent.SetContents(AValue: string);
|
|
begin
|
|
if FContents=AValue then Exit;
|
|
FContents:=AValue;
|
|
FModified:=True;
|
|
end;
|
|
|
|
constructor TWebFileContent.Create(const aFileName, aContents: String);
|
|
begin
|
|
FContents:=aContents;
|
|
FFileName:=aFileName;
|
|
end;
|
|
|
|
|
|
{ TPas2jsWebFS }
|
|
|
|
function TPas2jsWebFS.FileExists(const aFileName: String): Boolean;
|
|
begin
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': FileExists(',aFileName,')');
|
|
{$ENDIF}
|
|
Result:=FCache.HasFile(NormalizeFileName(aFileName));
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': FileExists(',aFileName,') : ',Result);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function TPas2jsWebFS.FindCustomJSFileName(const aFilename: string): String;
|
|
begin
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': FindCustomJSFileName(',aFileName,')');
|
|
{$ENDIF}
|
|
Result:=NormalizeFileName(aFileName);
|
|
If not FCache.HasFile(Result) then
|
|
Result:='';
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': FindCustomJSFileName(',aFileName,'): ',Result);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function TPas2jsWebFS.FindIncludeFileName(const aFilename, SrcDir, ModuleDir: string; Mode: TModeSwitch
|
|
): String;
|
|
begin
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': FindIncludeFileName(',aFileName,',',ModuleDir,')');
|
|
{$ENDIF}
|
|
Result:=NormalizeFileName(aFileName);
|
|
If not FCache.HasFile(Result) then
|
|
Result:='';
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': FindIncludeFileName(',aFileName,') : ',Result);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
class function TPas2jsWebFS.NormalizeFileName(const aFileName: String): String;
|
|
begin
|
|
Result:=LowerCase(ExtractFileName(aFileName));
|
|
end;
|
|
|
|
function TPas2jsWebFS.FindSourceFileName(const aFilename: string): String;
|
|
begin
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': FindSourceFileName(',aFileName,')');
|
|
{$ENDIF}
|
|
Result:=NormalizeFileName(aFileName);
|
|
If not FCache.HasFile(Result) then
|
|
Result:='';
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': FindSourceFileName(',aFileName,') : ',Result);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
constructor TPas2jsWebFS.Create;
|
|
begin
|
|
inherited Create;
|
|
FCache:=TWebFilesCache.Create;
|
|
end;
|
|
|
|
function TPas2jsWebFS.CreateResolver: TPas2jsFSResolver;
|
|
begin
|
|
Result:=TPas2jsWebResolver.Create(Self);
|
|
end;
|
|
|
|
function TPas2jsWebFS.FindUnitFileName(const aUnitname, InFilename, ModuleDir: string; out IsForeign: boolean): String;
|
|
begin
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': FindUnitFileName(',aUnitName,',',InFilename,',',ModuleDir,')');
|
|
{$ENDIF}
|
|
Result:=NormalizeFileName(aUnitName+'.pas');
|
|
isForeign:=False;
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': FindUnitFileName(',aUnitName,') : ',Result);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function TPas2jsWebFS.FindUnitJSFileName(const aUnitFilename: string): String;
|
|
begin
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': FindUnitJSFileName(',aUnitFileName,')');
|
|
{$ENDIF}
|
|
Result:=NormalizeFileName(aUnitFileName);
|
|
{$IFDEF VERBOSEWEBCACHE}
|
|
Writeln(ClassName,': FindUnitJSFileName(',aUnitFileName,') : ',Result);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function TPas2jsWebFS.LoadFile(Filename: string; Binary: boolean): TPas2jsFile;
|
|
|
|
begin
|
|
Result:=TPas2jsWebFile.Create(Self,FileName);
|
|
Result.Load(True,False);
|
|
end;
|
|
|
|
(*
|
|
// Check if we should not be using this instead, as the compiler outputs UTF8 ?
|
|
// Found on
|
|
// https://weblog.rogueamoeba.com/2017/02/27/javascript-correctly-converting-a-byte-array-to-a-utf-8-string/
|
|
function stringFromUTF8Array(data)
|
|
{
|
|
const extraByteMap = [ 1, 1, 1, 1, 2, 2, 3, 0 ];
|
|
var count = data.length;
|
|
var str = "";
|
|
|
|
for (var index = 0;index < count;)
|
|
{
|
|
var ch = data[index++];
|
|
if (ch & 0x80)
|
|
{
|
|
var extra = extraByteMap[(ch >> 3) & 0x07];
|
|
if (!(ch & 0x40) || !extra || ((index + extra) > count))
|
|
return null;
|
|
|
|
ch = ch & (0x3F >> extra);
|
|
for (;extra > 0;extra -= 1)
|
|
{
|
|
var chx = data[index++];
|
|
if ((chx & 0xC0) != 0x80)
|
|
return null;
|
|
|
|
ch = (ch << 6) | (chx & 0x3F);
|
|
}
|
|
}
|
|
|
|
str += String.fromCharCode(ch);
|
|
}
|
|
|
|
return str;
|
|
}
|
|
*)
|
|
procedure TPas2jsWebFS.SaveToFile(ms: TFPJSStream; Filename: string);
|
|
|
|
Var
|
|
aContent : String;
|
|
i : Integer;
|
|
v : JSValue;
|
|
|
|
begin
|
|
aContent:='';
|
|
for I:=0 to MS.Length-1 do
|
|
begin
|
|
v:=MS[i];
|
|
{AllowWriteln}
|
|
Writeln('Char ',i,'(',v,') : ',TJSString.fromCharCode(v));
|
|
{AllowWriteln-}
|
|
aContent:=aContent+TJSString.fromCharCode(MS[i]);
|
|
end;
|
|
SetFileContent(FileName,aContent);
|
|
end;
|
|
|
|
function TPas2jsWebFS.SetFileContent(const aFileName, aContents: String): Boolean;
|
|
begin
|
|
Result:=FCache.SetFileContent(NormalizeFileName(aFileName),aContents);
|
|
end;
|
|
|
|
function TPas2jsWebFS.GetFileContent(const aFileName: String): String;
|
|
begin
|
|
Result:=FCache.GetFileContent(NormalizeFileName(aFileName));
|
|
end;
|
|
|
|
function TPas2jsWebFS.LoadFile(aFileName: String; OnLoaded: TLoadFileEvent): Boolean;
|
|
|
|
Var
|
|
FN : String;
|
|
aURL : String;
|
|
LF : TLoadFileRequest;
|
|
|
|
begin
|
|
FN:=NormalizeFileName(aFileName);
|
|
Result:=Not FCache.HasFile(FN);
|
|
if Not result then
|
|
begin
|
|
// It is already loaded
|
|
if Assigned(OnLoaded) then
|
|
OnLoaded(Self,aFileName,'')
|
|
end
|
|
else
|
|
begin
|
|
// Not yet already loaded
|
|
aURL:=IncludeTrailingPathDelimiter(LoadBaseURL)+FN;
|
|
LF:=TLoadFileRequest.Create(Self,aFileName,OnLoaded);
|
|
LF.DoLoad(aURL);
|
|
end;
|
|
end;
|
|
|
|
function TPas2jsWebFS.LoadFiles(aList: TStrings; OnLoaded: TLoadFileEvent
|
|
): Integer;
|
|
|
|
Var
|
|
i: Integer;
|
|
|
|
begin
|
|
Result:=0;
|
|
For I:=0 to aList.Count-1 do
|
|
if LoadFile(aList[i],OnLoaded) then
|
|
Inc(Result);
|
|
end;
|
|
|
|
function TPas2jsWebFS.LoadFiles(aList: array of String; OnLoaded: TLoadFileEvent
|
|
): integer;
|
|
|
|
Var
|
|
i: Integer;
|
|
|
|
begin
|
|
Result:=0;
|
|
For I:=0 to Length(aList)-1 do
|
|
if LoadFile(aList[i],OnLoaded) then
|
|
Inc(Result);
|
|
end;
|
|
|
|
{ TPas2jsWebResolver }
|
|
|
|
function TPas2jsWebResolver.GetWebFS: TPas2jsWebFS;
|
|
begin
|
|
Result:=TPas2jsWebFS(FS)
|
|
end;
|
|
|
|
|
|
|
|
end.
|
|
|