mirror of
https://gitlab.com/freepascal.org/fpc/pas2js.git
synced 2025-04-15 16:39:23 +02:00
371 lines
9.5 KiB
ObjectPascal
371 lines
9.5 KiB
ObjectPascal
program server;
|
|
|
|
{$IF FPC_FULLVERSION<30101}
|
|
{$ERROR You need at least fpc 3.1.1}
|
|
{$ENDIF}
|
|
|
|
uses
|
|
{$IFDEF UNIX}cthreads,{$ENDIF}
|
|
sysutils, classes, fpjson, contnrs, syncobjs, custhttpapp, fpwebfile,
|
|
httproute, dirwatch, httpdefs;
|
|
|
|
Type
|
|
TDirWatcher = Class;
|
|
THTTPApplication = Class;
|
|
|
|
{ TCompileItem }
|
|
|
|
TCompileItem = Class(TCollectionItem)
|
|
private
|
|
FCommandLine: string;
|
|
FFileName: string;
|
|
FOutput : TStrings;
|
|
FThread: TThread;
|
|
function GetOutput: TStrings;
|
|
Public
|
|
Property FileName : string Read FFileName Write FFileName;
|
|
Property CommandLine : string Read FCommandLine Write FCommandLine;
|
|
Property Output : TStrings Read GetOutput;
|
|
Property Thread : TThread Read FThread;
|
|
end;
|
|
|
|
{ TCompiles }
|
|
|
|
TCompiles = Class(TCollection)
|
|
private
|
|
function GetC(AIndex : Integer): TCompileItem;
|
|
Public
|
|
Property Compiles[AIndex : Integer] : TCompileItem Read GetC; default;
|
|
end;
|
|
|
|
|
|
{ TCompileThread }
|
|
|
|
TCompileThread = class(TThread)
|
|
private
|
|
FItem: TCompileItem;
|
|
procedure SetItem(AValue: TCompileItem);
|
|
Public
|
|
Constructor create(aItem : TCompileItem);
|
|
Procedure Execute; override;
|
|
Property Item : TCompileItem read FItem write SetItem;
|
|
end;
|
|
|
|
{ TDirWatcher }
|
|
|
|
TDirWatcher = Class(TComponent)
|
|
Private
|
|
FApp : THTTPApplication;
|
|
FDW : TDirWatch;
|
|
procedure DoChange(Sender: TObject; aEntry: TDirectoryEntry; AEvents: TFileEvents);
|
|
Public
|
|
Constructor Create(App : THTTPApplication; ADir : String); reintroduce;
|
|
Destructor Destroy; override;
|
|
end;
|
|
|
|
{ THTTPApplication }
|
|
|
|
THTTPApplication = Class(TCustomHTTPApplication)
|
|
private
|
|
FProjectFile: String;
|
|
FStatusLock : TCriticalSection;
|
|
FQuiet: Boolean;
|
|
FWatch: Boolean;
|
|
FDW : TDirWatcher;
|
|
FStatusList : TFPObjectList;
|
|
FCompiles : TCompiles;
|
|
Procedure AddToStatus(AEntry : TDirectoryEntry; AEvents : TFileEvents);
|
|
procedure DoStatusRequest(ARequest: TRequest; AResponse: TResponse);
|
|
procedure DoRecompile(ARequest: TRequest; AResponse: TResponse);
|
|
function ScheduleCompile(const aProjectFile: String; ACommandLine : String = ''): Integer;
|
|
procedure StartWatch(ADir: String);
|
|
procedure Usage(Msg: String);
|
|
public
|
|
Constructor Create(AOWner : TComponent); override;
|
|
Destructor Destroy; override;
|
|
procedure DoLog(EventType: TEventType; const Msg: String); override;
|
|
Procedure DoRun; override;
|
|
property Quiet : Boolean read FQuiet Write FQuiet;
|
|
Property Watch : Boolean Read FWatch Write FWatch;
|
|
Property ProjectFile : String Read FProjectFile Write FProjectFile;
|
|
end;
|
|
|
|
|
|
{ TCompileThread }
|
|
|
|
procedure TCompileThread.SetItem(AValue: TCompileItem);
|
|
begin
|
|
if FItem=AValue then Exit;
|
|
FItem:=AValue;
|
|
end;
|
|
|
|
constructor TCompileThread.create(aItem: TCompileItem);
|
|
begin
|
|
|
|
end;
|
|
|
|
procedure TCompileThread.Execute;
|
|
begin
|
|
|
|
end;
|
|
|
|
{ TCompiles }
|
|
|
|
function TCompiles.GetC(AIndex : Integer): TCompileItem;
|
|
begin
|
|
Result:=Items[Aindex] as TCompileItem;
|
|
end;
|
|
|
|
{ TCompileItem }
|
|
|
|
function TCompileItem.GetOutput: TStrings;
|
|
begin
|
|
If (FOutput=Nil) then
|
|
FOutput:=TStringList.Create;
|
|
Result:=FOutput;
|
|
end;
|
|
|
|
|
|
{ TDirWatcher }
|
|
|
|
procedure TDirWatcher.DoChange(Sender: TObject; aEntry: TDirectoryEntry; AEvents: TFileEvents);
|
|
begin
|
|
if Assigned(FApp) then
|
|
FApp.AddToStatus(AEntry,AEvents);
|
|
end;
|
|
|
|
constructor TDirWatcher.Create(App: THTTPApplication; ADir: String);
|
|
begin
|
|
Inherited create(APP);
|
|
FApp:=App;
|
|
FDW:=TDirwatch.Create(Self);
|
|
FDW.AddWatch(ADir,allEvents);
|
|
FDW.OnChange:=@DoChange;
|
|
TThread.ExecuteInThread(@FDW.StartWatch);
|
|
end;
|
|
|
|
destructor TDirWatcher.Destroy;
|
|
begin
|
|
FApp:=Nil;
|
|
FDW.Terminate;
|
|
FreeAndNil(FDW);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
{ THTTPApplication }
|
|
|
|
procedure THTTPApplication.DoLog(EventType: TEventType; const Msg: String);
|
|
begin
|
|
if Quiet then
|
|
exit;
|
|
if IsConsole then
|
|
Writeln(FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz',Now),' [',EventType,'] ',Msg)
|
|
else
|
|
inherited DoLog(EventType, Msg);
|
|
end;
|
|
|
|
procedure THTTPApplication.Usage(Msg : String);
|
|
|
|
begin
|
|
if (Msg<>'') then
|
|
Writeln('Error: ',Msg);
|
|
Writeln('Usage ',ExtractFileName(ParamStr(0)),' [options] ');
|
|
Writeln('Where options is one or more of : ');
|
|
Writeln('-d --directory=dir Base directory from which to serve files.');
|
|
Writeln(' Default is current working directory: ',GetCurrentDir);
|
|
Writeln('-h --help This help text');
|
|
Writeln('-i --indexpage=name Directory index page to use (default: index.html)');
|
|
Writeln('-n --noindexpage Do not allow index page.');
|
|
Writeln('-p --port=NNNN TCP/IP port to listen on (default is 3000)');
|
|
Writeln('-q --quiet Do not write diagnostic messages');
|
|
Writeln('-w --watch Watch directory for changes');
|
|
Halt(Ord(Msg<>''));
|
|
end;
|
|
|
|
constructor THTTPApplication.Create(AOWner: TComponent);
|
|
begin
|
|
inherited Create(AOWner);
|
|
FStatusLock:=TCriticalSection.Create;
|
|
FStatusList:=TFPObjectList.Create(False);
|
|
FCompiles:=TCompiles.Create(TCompileItem);
|
|
end;
|
|
|
|
destructor THTTPApplication.Destroy;
|
|
begin
|
|
FStatusLock.Enter;
|
|
try
|
|
FreeAndNil(FCompiles);
|
|
FreeAndNil(FStatusList);
|
|
finally
|
|
FStatusLock.Leave;
|
|
end;
|
|
FreeAndNil(FStatusLock);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure THTTPApplication.StartWatch(ADir : String);
|
|
|
|
begin
|
|
FDW:=TDirWatcher.Create(Self,ADir);
|
|
end;
|
|
|
|
procedure THTTPApplication.AddToStatus(AEntry: TDirectoryEntry; AEvents: TFileEvents);
|
|
begin
|
|
Log(etDebug,'File change detected: %s (%s)',[AEntry.name,FileEventsToStr(AEvents)]);
|
|
FStatusLock.Enter;
|
|
try
|
|
FStatusList.Add(TJSONObject.Create(['action','file','name',AEntry.name,'events',FileEventsToStr(AEvents)]));
|
|
finally
|
|
FStatusLock.Leave;
|
|
end;
|
|
end;
|
|
|
|
procedure THTTPApplication.DoStatusRequest(ARequest : TRequest; AResponse : TResponse);
|
|
|
|
Var
|
|
R,O : TJSONObject;
|
|
A : TJSONArray;
|
|
I : integer;
|
|
begin
|
|
Log(etDebug,'Status request from: %s',[ARequest.RemoteAddress]);
|
|
R:=Nil;
|
|
try
|
|
FStatusLock.Enter;
|
|
try
|
|
if (FStatusList.Count=0) then
|
|
R:=TJSONObject.Create(['ping',True])
|
|
else
|
|
begin
|
|
O:=FStatusList[0] as TJSONObject;
|
|
FStatusList.Delete(0);
|
|
if O.Get('action','')<>'file' then
|
|
R:=O
|
|
else
|
|
begin
|
|
// If first event is file event, then add and delete all file events in list.
|
|
A:=TJSONArray.Create([O]);
|
|
O.Delete('action');
|
|
R:=TJSONObject.Create(['action','sync','files',A]);
|
|
For I:=FStatusList.Count-1 downto 0 do
|
|
begin
|
|
O:=FStatusList[0] as TJSONObject;
|
|
if (O.Get('action','')='file') then
|
|
begin
|
|
A.Add(O);
|
|
O.Delete('action');
|
|
FStatusList.Delete(I);
|
|
end;
|
|
end;
|
|
end
|
|
end;
|
|
finally
|
|
FStatusLock.Leave;
|
|
end;
|
|
AResponse.ContentType:='application/json';
|
|
AResponse.Content:=R.AsJSON;
|
|
AResponse.SendResponse;
|
|
finally
|
|
R.Free;
|
|
end;
|
|
end;
|
|
|
|
Function THTTPApplication.ScheduleCompile(const aProjectFile : String; ACommandLine : String = '') : Integer;
|
|
|
|
Var
|
|
CI : TCompileItem;
|
|
|
|
begin
|
|
CI:=FCompiles.Add as TCompileItem;
|
|
CI.FileName:=AProjectFile;
|
|
CI.FThread:=TCompileThread.Create(CI);
|
|
Result:=CI.ID;
|
|
end;
|
|
|
|
procedure THTTPApplication.DoRecompile(ARequest: TRequest; AResponse: TResponse);
|
|
|
|
Var
|
|
ID : Integer;
|
|
PF,CL : String;
|
|
|
|
begin
|
|
PF:=ARequest.ContentFields.Values['ProjectFile'];
|
|
CL:=ARequest.ContentFields.Values['CommandLine'];
|
|
if PF='' then
|
|
PF:=ProjectFile;
|
|
If (PF='') then
|
|
begin
|
|
AResponse.Code:=404;
|
|
AResponse.CodeText:='No project file';
|
|
AResponse.ContentType:='application/json';
|
|
AResponse.Content:='{ "success" : false, "message": "no project file set or provided" }';
|
|
end
|
|
else
|
|
begin
|
|
ID:=ScheduleCompile(PF,CL);
|
|
AResponse.Code:=200;
|
|
AResponse.ContentType:='application/json';
|
|
AResponse.Content:=Format('{ "success" : true, "file": "%s", "commandLine" : "%s", "compileID": %d }',[StringToJSONString(PF),StringToJSONString(CL),ID]);
|
|
end;
|
|
end;
|
|
|
|
procedure THTTPApplication.DoRun;
|
|
|
|
Var
|
|
S,IndexPage,D : String;
|
|
|
|
begin
|
|
S:=Checkoptions('hqd:ni:p:wc::',['help','quiet','noindexpage','directory:','port:','indexpage:','watch','compile::']);
|
|
if (S<>'') or HasOption('h','help') then
|
|
usage(S);
|
|
Quiet:=HasOption('q','quiet');
|
|
Watch:=HasOption('w','watch');
|
|
|
|
Port:=StrToIntDef(GetOptionValue('p','port'),3000);
|
|
D:=GetOptionValue('d','directory');
|
|
if D='' then
|
|
D:=GetCurrentDir;
|
|
Log(etInfo,'Listening on port %d, serving files from directory: %s',[Port,D]);
|
|
{$ifdef unix}
|
|
{$ifdef darwin}
|
|
MimeTypesFile:='/private/etc/apache2/mime.types';
|
|
{$else}
|
|
MimeTypesFile:='/etc/mime.types';
|
|
{$endif}
|
|
{$endif}
|
|
if Watch then
|
|
StartWatch(D);
|
|
httprouter.RegisterRoute('$sys/status',rmGet,@DoStatusRequest);
|
|
if Hasoption('c','compile') then
|
|
begin
|
|
ProjectFile:=GetOptionValue('c','compile');
|
|
if ProjectFile='' then
|
|
ProjectFile:=IncludeTrailingPathDelimiter(D)+'server.lpr';
|
|
If Not FileExists(ProjectFile) then
|
|
ProjectFile:=IncludeTrailingPathDelimiter(D)+'server.lpr';
|
|
httprouter.RegisterRoute('$sys/compile',rmPost,@DoRecompile);
|
|
end;
|
|
TSimpleFileModule.BaseDir:=IncludeTrailingPathDelimiter(D);
|
|
TSimpleFileModule.OnLog:=@Log;
|
|
If not HasOption('n','noindexpage') then
|
|
begin
|
|
IndexPage:=GetOptionValue('i','indexpage');
|
|
if IndexPage='' then
|
|
IndexPage:='index.html';
|
|
Log(etInfo,'Using index page %s',[IndexPage]);
|
|
TSimpleFileModule.IndexPageName:=IndexPage;
|
|
end;
|
|
TSimpleFileModule.RegisterDefaultRoute;
|
|
inherited;
|
|
end;
|
|
|
|
Var
|
|
Application : THTTPApplication;
|
|
|
|
begin
|
|
Application:=THTTPApplication.Create(Nil);
|
|
Application.Initialize;
|
|
Application.Run;
|
|
Application.Free;
|
|
end.
|
|
|