server.lpr 9.5 KB

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