fpc/utils/pas2js/webfilecache.pp
michael 3db8f5c91f * Fix compilation
git-svn-id: trunk@47754 -
2020-12-11 13:34:44 +00:00

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.