server.lpr 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. program server;
  2. uses
  3. {$IFDEF UNIX}cthreads,{$ENDIF}
  4. sysutils, classes, fpjson, contnrs, syncobjs, custhttpapp, fpwebfile,
  5. httproute, dirwatch, httpdefs;
  6. Type
  7. TDirWatcher = Class;
  8. THTTPApplication = Class;
  9. { TCompileItem }
  10. TCompileItem = Class(TCollectionItem)
  11. private
  12. FCommandLine: string;
  13. FFileName: string;
  14. FOutput : TStrings;
  15. FThread: TThread;
  16. function GetOutput: TStrings;
  17. Public
  18. Property FileName : string Read FFileName Write FFileName;
  19. Property CommandLine : string Read FCommandLine Write FCommandLine;
  20. Property Output : TStrings Read GetOutput;
  21. Property Thread : TThread Read FThread;
  22. end;
  23. { TCompiles }
  24. TCompiles = Class(TCollection)
  25. private
  26. function GetC(AIndex : Integer): TCompileItem;
  27. Public
  28. Property Compiles[AIndex : Integer] : TCompileItem Read GetC; default;
  29. end;
  30. { TCompileThread }
  31. TCompileThread = class(TThread)
  32. private
  33. FItem: TCompileItem;
  34. procedure SetItem(AValue: TCompileItem);
  35. Public
  36. Constructor create(aItem : TCompileItem);
  37. Procedure Execute; override;
  38. Property Item : TCompileItem read FItem write SetItem;
  39. end;
  40. { TDirWatcher }
  41. TDirWatcher = Class(TComponent)
  42. Private
  43. FApp : THTTPApplication;
  44. FDW : TDirWatch;
  45. procedure DoChange(Sender: TObject; aEntry: TDirectoryEntry; AEvents: TFileEvents);
  46. Public
  47. Constructor Create(App : THTTPApplication; ADir : String); reintroduce;
  48. Destructor Destroy; override;
  49. end;
  50. { THTTPApplication }
  51. THTTPApplication = Class(TCustomHTTPApplication)
  52. private
  53. FProjectFile: String;
  54. FStatusLock : TCriticalSection;
  55. FQuiet: Boolean;
  56. FWatch: Boolean;
  57. FDW : TDirWatcher;
  58. FStatusList : TFPObjectList;
  59. FCompiles : TCompiles;
  60. Procedure AddToStatus(AEntry : TDirectoryEntry; AEvents : TFileEvents);
  61. procedure DoStatusRequest(ARequest: TRequest; AResponse: TResponse);
  62. procedure DoRecompile(ARequest: TRequest; AResponse: TResponse);
  63. function ScheduleCompile(const aProjectFile: String; ACommandLine : String = ''): Integer;
  64. procedure StartWatch(ADir: String);
  65. procedure Usage(Msg: String);
  66. public
  67. Constructor Create(AOWner : TComponent); override;
  68. Destructor Destroy; override;
  69. procedure DoLog(EventType: TEventType; const Msg: String); override;
  70. Procedure DoRun; override;
  71. property Quiet : Boolean read FQuiet Write FQuiet;
  72. Property Watch : Boolean Read FWatch Write FWatch;
  73. Property ProjectFile : String Read FProjectFile Write FProjectFile;
  74. end;
  75. { TCompileThread }
  76. procedure TCompileThread.SetItem(AValue: TCompileItem);
  77. begin
  78. if FItem=AValue then Exit;
  79. FItem:=AValue;
  80. end;
  81. constructor TCompileThread.create(aItem: TCompileItem);
  82. begin
  83. end;
  84. procedure TCompileThread.Execute;
  85. begin
  86. end;
  87. { TCompiles }
  88. function TCompiles.GetC(AIndex : Integer): TCompileItem;
  89. begin
  90. Result:=Items[Aindex] as TCompileItem;
  91. end;
  92. { TCompileItem }
  93. function TCompileItem.GetOutput: TStrings;
  94. begin
  95. If (FOutput=Nil) then
  96. FOutput:=TStringList.Create;
  97. Result:=FOutput;
  98. end;
  99. { TDirWatcher }
  100. procedure TDirWatcher.DoChange(Sender: TObject; aEntry: TDirectoryEntry; AEvents: TFileEvents);
  101. begin
  102. if Assigned(FApp) then
  103. FApp.AddToStatus(AEntry,AEvents);
  104. end;
  105. constructor TDirWatcher.Create(App: THTTPApplication; ADir: String);
  106. begin
  107. Inherited create(APP);
  108. FApp:=App;
  109. FDW:=TDirwatch.Create(Self);
  110. FDW.AddWatch(ADir,allEvents);
  111. FDW.OnChange:=@DoChange;
  112. TThread.ExecuteInThread(@FDW.StartWatch);
  113. end;
  114. destructor TDirWatcher.Destroy;
  115. begin
  116. FApp:=Nil;
  117. FDW.Terminate;
  118. FreeAndNil(FDW);
  119. inherited Destroy;
  120. end;
  121. { THTTPApplication }
  122. procedure THTTPApplication.DoLog(EventType: TEventType; const Msg: String);
  123. begin
  124. if Quiet then
  125. exit;
  126. if IsConsole then
  127. Writeln(FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz',Now),' [',EventType,'] ',Msg)
  128. else
  129. inherited DoLog(EventType, Msg);
  130. end;
  131. procedure THTTPApplication.Usage(Msg : String);
  132. begin
  133. if (Msg<>'') then
  134. Writeln('Error: ',Msg);
  135. Writeln('Usage ',ExtractFileName(ParamStr(0)),' [options] ');
  136. Writeln('Where options is one or more of : ');
  137. Writeln('-d --directory=dir Base directory from which to serve files.');
  138. Writeln(' Default is current working directory: ',GetCurrentDir);
  139. Writeln('-h --help This help text');
  140. Writeln('-i --indexpage=name Directory index page to use (default: index.html)');
  141. Writeln('-n --noindexpage Do not allow index page.');
  142. Writeln('-p --port=NNNN TCP/IP port to listen on (default is 3000)');
  143. Writeln('-q --quiet Do not write diagnostic messages');
  144. Writeln('-w --watch Watch directory for changes');
  145. Halt(Ord(Msg<>''));
  146. end;
  147. constructor THTTPApplication.Create(AOWner: TComponent);
  148. begin
  149. inherited Create(AOWner);
  150. FStatusLock:=TCriticalSection.Create;
  151. FStatusList:=TFPObjectList.Create(False);
  152. FCompiles:=TCompiles.Create(TCompileItem);
  153. end;
  154. destructor THTTPApplication.Destroy;
  155. begin
  156. FStatusLock.Enter;
  157. try
  158. FreeAndNil(FCompiles);
  159. FreeAndNil(FStatusList);
  160. finally
  161. FStatusLock.Leave;
  162. end;
  163. FreeAndNil(FStatusLock);
  164. inherited Destroy;
  165. end;
  166. procedure THTTPApplication.StartWatch(ADir : String);
  167. begin
  168. FDW:=TDirWatcher.Create(Self,ADir);
  169. end;
  170. procedure THTTPApplication.AddToStatus(AEntry: TDirectoryEntry; AEvents: TFileEvents);
  171. begin
  172. Log(etDebug,'File change detected: %s (%s)',[AEntry.name,FileEventsToStr(AEvents)]);
  173. FStatusLock.Enter;
  174. try
  175. FStatusList.Add(TJSONObject.Create(['action','file','name',AEntry.name,'events',FileEventsToStr(AEvents)]));
  176. finally
  177. FStatusLock.Leave;
  178. end;
  179. end;
  180. procedure THTTPApplication.DoStatusRequest(ARequest : TRequest; AResponse : TResponse);
  181. Var
  182. R,O : TJSONObject;
  183. A : TJSONArray;
  184. I : integer;
  185. begin
  186. Log(etDebug,'Status request from: %s',[ARequest.RemoteAddress]);
  187. R:=Nil;
  188. try
  189. FStatusLock.Enter;
  190. try
  191. if (FStatusList.Count=0) then
  192. R:=TJSONObject.Create(['ping',True])
  193. else
  194. begin
  195. O:=FStatusList[0] as TJSONObject;
  196. FStatusList.Delete(0);
  197. if O.Get('action','')<>'file' then
  198. R:=O
  199. else
  200. begin
  201. // If first event is file event, then add and delete all file events in list.
  202. A:=TJSONArray.Create([O]);
  203. O.Delete('action');
  204. R:=TJSONObject.Create(['action','sync','files',A]);
  205. For I:=FStatusList.Count-1 downto 0 do
  206. begin
  207. O:=FStatusList[0] as TJSONObject;
  208. if (O.Get('action','')='file') then
  209. begin
  210. A.Add(O);
  211. O.Delete('action');
  212. FStatusList.Delete(I);
  213. end;
  214. end;
  215. end
  216. end;
  217. finally
  218. FStatusLock.Leave;
  219. end;
  220. AResponse.ContentType:='application/json';
  221. AResponse.Content:=R.AsJSON;
  222. AResponse.SendResponse;
  223. finally
  224. R.Free;
  225. end;
  226. end;
  227. Function THTTPApplication.ScheduleCompile(const aProjectFile : String; ACommandLine : String = '') : Integer;
  228. Var
  229. CI : TCompileItem;
  230. begin
  231. CI:=FCompiles.Add as TCompileItem;
  232. CI.FileName:=AProjectFile;
  233. CI.FThread:=TCompileThread.Create(CI);
  234. Result:=CI.ID;
  235. end;
  236. procedure THTTPApplication.DoRecompile(ARequest: TRequest; AResponse: TResponse);
  237. Var
  238. ID : Integer;
  239. PF,CL : String;
  240. begin
  241. PF:=ARequest.ContentFields.Values['ProjectFile'];
  242. CL:=ARequest.ContentFields.Values['CommandLine'];
  243. if PF='' then
  244. PF:=ProjectFile;
  245. If (PF='') then
  246. begin
  247. AResponse.Code:=404;
  248. AResponse.CodeText:='No project file';
  249. AResponse.ContentType:='application/json';
  250. AResponse.Content:='{ "success" : false, "message": "no project file set or provided" }';
  251. end
  252. else
  253. begin
  254. ID:=ScheduleCompile(PF,CL);
  255. AResponse.Code:=200;
  256. AResponse.ContentType:='application/json';
  257. AResponse.Content:=Format('{ "success" : true, "file": "%s", "commandLine" : "%s", "compileID": %d }',[StringToJSONString(PF),StringToJSONString(CL),ID]);
  258. end;
  259. end;
  260. procedure THTTPApplication.DoRun;
  261. Var
  262. S,IndexPage,D : String;
  263. begin
  264. S:=Checkoptions('hqd:ni:p:wc::',['help','quiet','noindexpage','directory:','port:','indexpage:','watch','compile::']);
  265. if (S<>'') or HasOption('h','help') then
  266. usage(S);
  267. Quiet:=HasOption('q','quiet');
  268. Watch:=HasOption('w','watch');
  269. Port:=StrToIntDef(GetOptionValue('p','port'),3000);
  270. D:=GetOptionValue('d','directory');
  271. if D='' then
  272. D:=GetCurrentDir;
  273. Log(etInfo,'Listening on port %d, serving files from directory: %s',[Port,D]);
  274. {$ifdef unix}
  275. {$ifdef darwin}
  276. MimeTypesFile:='/private/etc/apache2/mime.types';
  277. {$else}
  278. MimeTypesFile:='/etc/mime.types';
  279. {$endif}
  280. {$endif}
  281. if Watch then
  282. StartWatch(D);
  283. httprouter.RegisterRoute('$sys/status',rmGet,@DoStatusRequest);
  284. if Hasoption('c','compile') then
  285. begin
  286. ProjectFile:=GetOptionValue('c','compile');
  287. if ProjectFile='' then
  288. ProjectFile:=IncludeTrailingPathDelimiter(D)+'server.lpr';
  289. If Not FileExists(ProjectFile) then
  290. ProjectFile:=IncludeTrailingPathDelimiter(D)+'server.lpr';
  291. httprouter.RegisterRoute('$sys/compile',rmPost,@DoRecompile);
  292. end;
  293. TSimpleFileModule.BaseDir:=IncludeTrailingPathDelimiter(D);
  294. TSimpleFileModule.OnLog:=@Log;
  295. If not HasOption('n','noindexpage') then
  296. begin
  297. IndexPage:=GetOptionValue('i','indexpage');
  298. if IndexPage='' then
  299. IndexPage:='index.html';
  300. Log(etInfo,'Using index page %s',[IndexPage]);
  301. TSimpleFileModule.IndexPageName:=IndexPage;
  302. end;
  303. TSimpleFileModule.RegisterDefaultRoute;
  304. inherited;
  305. end;
  306. Var
  307. Application : THTTPApplication;
  308. begin
  309. Application:=THTTPApplication.Create(Nil);
  310. Application.Initialize;
  311. Application.Run;
  312. Application.Free;
  313. end.