123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- 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: string): String; override;
- function FindUnitFileName(const aUnitname, InFilename: 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: string): String;
- begin
- {$IFDEF VERBOSEWEBCACHE}
- Writeln(ClassName,': FindIncludeFileName(',aFileName,')');
- {$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: string; out IsForeign: boolean): String;
- begin
- {$IFDEF VERBOSEWEBCACHE}
- Writeln(ClassName,': FindUnitFileName(',aUnitName,')');
- {$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.
|