123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- 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.
|