mirror of
https://github.com/danieleteti/delphimvcframework.git
synced 2024-11-16 00:05:53 +01:00
543 lines
15 KiB
ObjectPascal
543 lines
15 KiB
ObjectPascal
|
// ***************************************************************************
|
||
|
//
|
||
|
// Delphi MVC Framework
|
||
|
//
|
||
|
// Copyright (c) 2010-2022 Daniele Teti and the DMVCFramework Team
|
||
|
//
|
||
|
// https://github.com/danieleteti/delphimvcframework
|
||
|
//
|
||
|
// ***************************************************************************
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
//
|
||
|
// ***************************************************************************
|
||
|
|
||
|
unit MVCFramework.View.Renderers.Lua;
|
||
|
|
||
|
{$WARNINGS OFF}
|
||
|
{$I dmvcframework.inc}
|
||
|
|
||
|
interface
|
||
|
|
||
|
uses
|
||
|
MVCFramework,
|
||
|
MVCFramework.Commons,
|
||
|
System.Generics.Collections,
|
||
|
Data.DB,
|
||
|
MVCFramework.View.Cache,
|
||
|
MVCFramework.Serializer.JsonDataObjects,
|
||
|
System.SysUtils, System.Classes;
|
||
|
|
||
|
type
|
||
|
TMVCLuaViewEngine = class(TMVCBaseViewEngine)
|
||
|
private
|
||
|
function GetCompiledFileName(const FileName: string): string;
|
||
|
public
|
||
|
procedure Execute(const ViewName: string;
|
||
|
const OutputStream: TStream); override;
|
||
|
protected
|
||
|
function IsCompiledVersionUpToDate(const FileName, CompiledFileName: string)
|
||
|
: Boolean; override;
|
||
|
public const
|
||
|
LOG_FILE_NAME = 'mvc_%s.log';
|
||
|
DEFAULT_VIEW_EXT = 'elua';
|
||
|
end;
|
||
|
|
||
|
implementation
|
||
|
|
||
|
uses
|
||
|
MVCFramework.DuckTyping,
|
||
|
LuaBind,
|
||
|
LuaBind.Filters.Text,
|
||
|
LuaBind.CustomType.DataSet,
|
||
|
LuaBind.Intf,
|
||
|
System.ioutils,
|
||
|
System.JSON,
|
||
|
JsonDataObjects, MVCFramework.Serializer.Commons, System.Rtti;
|
||
|
|
||
|
//function __lua_form_parameter(L: Plua_State): Integer; cdecl;
|
||
|
//var
|
||
|
// parname: string;
|
||
|
// res: string;
|
||
|
// // rq: TMVCWebRequest;
|
||
|
// p: Pointer;
|
||
|
// WebContext: TWebContext;
|
||
|
//begin
|
||
|
// if lua_gettop(L) <> 2 then
|
||
|
// begin
|
||
|
// luaL_error(L, PAnsiChar('Wrong parameters number'));
|
||
|
// Exit;
|
||
|
// end;
|
||
|
// parname := TLuaValue.GetTValueFromLuaValueType(TLuaValueType.lvtString, L,
|
||
|
// -1).AsString;
|
||
|
// lua_getfield(L, -2, '__self');
|
||
|
// p := lua_topointer(L, -1);
|
||
|
// WebContext := TWebContext(TObject(p));
|
||
|
// res := WebContext.Request.Params[parname];
|
||
|
// TLuaUtils.PushTValue(L, res);
|
||
|
// Result := 1;
|
||
|
//end;
|
||
|
//
|
||
|
//function __lua_headers_get_all(L: Plua_State): Integer; cdecl;
|
||
|
//var
|
||
|
// // parname: string;
|
||
|
// res: string;
|
||
|
// // rq: TMVCWebRequest;
|
||
|
// p: Pointer;
|
||
|
// WebContext: TWebContext;
|
||
|
//begin
|
||
|
// if lua_gettop(L) <> 1 then
|
||
|
// begin
|
||
|
// luaL_error(L, PAnsiChar('Wrong parameters number (0 expected)'));
|
||
|
// Exit;
|
||
|
// end;
|
||
|
// lua_getfield(L, -1, '__self');
|
||
|
// p := lua_topointer(L, -1);
|
||
|
// WebContext := TWebContext(TObject(p));
|
||
|
// res := WebContext.Request.RawWebRequest.Content;
|
||
|
// { TODO -oDaniele -cGeneral : Do not works }
|
||
|
// TLuaUtils.PushTValue(L, res);
|
||
|
// Result := 1;
|
||
|
//end;
|
||
|
//
|
||
|
//function __lua_headers(L: Plua_State): Integer; cdecl;
|
||
|
//var
|
||
|
// parname: string;
|
||
|
// res: string;
|
||
|
// // rq: TMVCWebRequest;
|
||
|
// p: Pointer;
|
||
|
// WebContext: TWebContext;
|
||
|
// // parvalue: string;
|
||
|
//begin
|
||
|
// if (lua_gettop(L) <> 2) then
|
||
|
// begin
|
||
|
// luaL_error(L, PAnsiChar('Wrong parameters number'));
|
||
|
// Exit;
|
||
|
// end;
|
||
|
//
|
||
|
// parname := TLuaValue.GetTValueFromLuaValueType(TLuaValueType.lvtString, L,
|
||
|
// -1).AsString;
|
||
|
// lua_getfield(L, -2, '__self');
|
||
|
// p := lua_topointer(L, -1);
|
||
|
// WebContext := TWebContext(TObject(p));
|
||
|
// res := WebContext.Request.RawWebRequest.GetFieldByName(parname.ToUpper);
|
||
|
// TLuaUtils.PushTValue(L, res);
|
||
|
// Result := 1;
|
||
|
//end;
|
||
|
//
|
||
|
//function __lua_set_http_code(L: Plua_State): Integer; cdecl;
|
||
|
//var
|
||
|
// // parname: string;
|
||
|
// // res: string;
|
||
|
// // rq: TMVCWebRequest;
|
||
|
// p: Pointer;
|
||
|
// WebContext: TWebContext;
|
||
|
// // parvalue: string;
|
||
|
// errocode: Integer;
|
||
|
//begin
|
||
|
// if lua_gettop(L) <> 2 then
|
||
|
// begin
|
||
|
// luaL_error(L, PAnsiChar('Wrong parameters number'));
|
||
|
// Exit;
|
||
|
// end;
|
||
|
//
|
||
|
// // setting the http return code request:http_code(404)
|
||
|
// errocode := TLuaValue.GetTValueFromLuaValueType(TLuaValueType.lvtInteger, L,
|
||
|
// -1).AsInteger;
|
||
|
// lua_getfield(L, -2, '__self');
|
||
|
// p := lua_topointer(L, -1);
|
||
|
//
|
||
|
// WebContext := TWebContext(TObject(p));
|
||
|
// WebContext.Response.StatusCode := errocode;
|
||
|
// Result := 0;
|
||
|
//end;
|
||
|
//
|
||
|
//function __lua_set_response_headers(L: Plua_State): Integer; cdecl;
|
||
|
//var
|
||
|
// parname: string;
|
||
|
// // res: string;
|
||
|
// // rq: TMVCWebRequest;
|
||
|
// p: Pointer;
|
||
|
// WebContext: TWebContext;
|
||
|
// parvalue: string;
|
||
|
//begin
|
||
|
// if lua_gettop(L) <> 3 then
|
||
|
// begin
|
||
|
// luaL_error(L, PAnsiChar('Wrong parameters number'));
|
||
|
// Exit;
|
||
|
// end;
|
||
|
//
|
||
|
// // setting an header request:headers(name, newvalue)
|
||
|
// parname := TLuaValue.GetTValueFromLuaValueType(TLuaValueType.lvtString, L,
|
||
|
// -2).AsString;
|
||
|
// parvalue := TLuaValue.GetTValueFromLuaValueType(TLuaValueType.lvtString, L,
|
||
|
// -1).AsString;
|
||
|
// lua_getfield(L, -3, '__self');
|
||
|
// p := lua_topointer(L, -1);
|
||
|
//
|
||
|
// WebContext := TWebContext(TObject(p));
|
||
|
// WebContext.Response.CustomHeaders.Values[parname] := parvalue;
|
||
|
// Result := 0;
|
||
|
//end;
|
||
|
//
|
||
|
//function __lua_post_parameter(L: Plua_State): Integer; cdecl;
|
||
|
//var
|
||
|
// parname: string;
|
||
|
// res: string;
|
||
|
// // rq: TMVCWebRequest;
|
||
|
// p: Pointer;
|
||
|
// WebContext: TWebContext;
|
||
|
//begin
|
||
|
// if lua_gettop(L) <> 2 then
|
||
|
// begin
|
||
|
// luaL_error(L, PAnsiChar('Wrong parameters number'));
|
||
|
// Exit;
|
||
|
// end;
|
||
|
// parname := TLuaValue.GetTValueFromLuaValueType(TLuaValueType.lvtString, L,
|
||
|
// -1).AsString;
|
||
|
// lua_getfield(L, -2, '__self');
|
||
|
// p := lua_topointer(L, -1);
|
||
|
// WebContext := TWebContext(TObject(p));
|
||
|
// res := WebContext.Request.ContentParam(parname);
|
||
|
// TLuaUtils.PushTValue(L, res);
|
||
|
// Result := 1;
|
||
|
//end;
|
||
|
//
|
||
|
//function __lua_set_header_field(L: Plua_State): Integer; cdecl;
|
||
|
//var
|
||
|
// parname: string;
|
||
|
// // res: string;
|
||
|
// // rq: TMVCWebRequest;
|
||
|
// p: Pointer;
|
||
|
// WebContext: TWebContext;
|
||
|
// parvalue: string;
|
||
|
//begin
|
||
|
// if lua_gettop(L) <> 2 then
|
||
|
// begin
|
||
|
// luaL_error(L, PAnsiChar('Wrong parameters number'));
|
||
|
// Exit;
|
||
|
// end;
|
||
|
// parname := TLuaValue.GetTValueFromLuaValueType(TLuaValueType.lvtString, L,
|
||
|
// -2).AsString;
|
||
|
// parvalue := TLuaValue.GetTValueFromLuaValueType(TLuaValueType.lvtString, L,
|
||
|
// -1).AsString;
|
||
|
// lua_getfield(L, -3, '__self');
|
||
|
// p := lua_topointer(L, -1);
|
||
|
// WebContext := TWebContext(TObject(p));
|
||
|
// WebContext.Response.SetCustomHeader(parname, parvalue);
|
||
|
// Result := 0;
|
||
|
//end;
|
||
|
//
|
||
|
//function __lua_get_parameter(L: Plua_State): Integer; cdecl;
|
||
|
//var
|
||
|
// parname: string;
|
||
|
// res: string;
|
||
|
// // rq: TMVCWebRequest;
|
||
|
// p: Pointer;
|
||
|
// WebContext: TWebContext;
|
||
|
//begin
|
||
|
// if lua_gettop(L) <> 2 then
|
||
|
// begin
|
||
|
// luaL_error(L, PAnsiChar('Wrong parameters number'));
|
||
|
// Exit;
|
||
|
// end;
|
||
|
// parname := TLuaValue.GetTValueFromLuaValueType(TLuaValueType.lvtString, L,
|
||
|
// -1).AsString;
|
||
|
// lua_getfield(L, -2, '__self');
|
||
|
// p := lua_topointer(L, -1);
|
||
|
// WebContext := TWebContext(TObject(p));
|
||
|
// res := WebContext.Request.QueryStringParam(parname);
|
||
|
// TLuaUtils.PushTValue(L, res);
|
||
|
// Result := 1;
|
||
|
//end;
|
||
|
|
||
|
// function __lua_stream_out(L: Plua_State): Integer; cdecl;
|
||
|
// var
|
||
|
// p: Pointer;
|
||
|
// WebContext: TWebContext;
|
||
|
// v: string;
|
||
|
// begin
|
||
|
// if lua_gettop(L) <> 2 then
|
||
|
// begin
|
||
|
// luaL_error(L, PAnsiChar('Wrong parameters number'));
|
||
|
// Exit;
|
||
|
// end;
|
||
|
// v := lua_tostring(L, -1);
|
||
|
// lua_getfield(L, -2, '__self');
|
||
|
// p := lua_topointer(L, -1);
|
||
|
//
|
||
|
// WebContext := TWebContext(TObject(p));
|
||
|
// raise Exception.Create('WIP');
|
||
|
// // TStringBuilder(WebContext.ReservedData).Append(v);
|
||
|
// Result := 0;
|
||
|
// end;
|
||
|
|
||
|
function __lua_stream_out(L: Plua_State): Integer; cdecl;
|
||
|
var
|
||
|
lPointerToWebContext, lPointerToStreamWriter: Pointer;
|
||
|
WebContext: TWebContext;
|
||
|
v: PAnsiChar;
|
||
|
lParCount: Integer;
|
||
|
lArr: TArray<Byte>;
|
||
|
begin
|
||
|
lParCount := lua_gettop(L);
|
||
|
if lua_gettop(L) <> 2 then
|
||
|
begin
|
||
|
luaL_error(L, PAnsiChar('Wrong parameters number'));
|
||
|
Exit;
|
||
|
end;
|
||
|
v := lua_tostring(L, -1);
|
||
|
|
||
|
lPointerToStreamWriter := lua_topointer(L, -2);
|
||
|
// lua_getfield(L, -2, '__stringbuilder');
|
||
|
// lPointerToStringBuilder := lua_topointer(L, -1);
|
||
|
|
||
|
// lua_getfield(L, -2, '__self');
|
||
|
// lPointerToWebContext := lua_topointer(L, -1);
|
||
|
//TWebContext(TObject(lPointerToWebContext)).Response.Content := v;
|
||
|
|
||
|
// SetLength(lArr, Length(v));
|
||
|
// CopyArray(lArr, v, TypeInfo(byte), Length(lArr));
|
||
|
|
||
|
TStreamWriter(lPointerToStreamWriter).Write(UTF8Encode(v));
|
||
|
Result := 0;
|
||
|
end;
|
||
|
|
||
|
|
||
|
// var
|
||
|
// s: string;
|
||
|
// o: TObject;
|
||
|
// C: TWebContext;
|
||
|
// begin
|
||
|
// if lua_gettop(L) <> 2 then
|
||
|
// begin
|
||
|
// luaL_error(L, PAnsiChar('Wrong parameters number'));
|
||
|
// Exit;
|
||
|
// end;
|
||
|
//
|
||
|
// if lua_isstring(L, - 1) = 1 then
|
||
|
// begin
|
||
|
// s := lua_tostring(L, - 1);
|
||
|
// // lua_pop(L, 1);
|
||
|
// end
|
||
|
// else
|
||
|
// begin
|
||
|
// luaL_error(L, PAnsiChar('Type mismatch, expected String'));
|
||
|
// Exit;
|
||
|
// end;
|
||
|
//
|
||
|
// // if lua_islightuserdata(L, - 2) then
|
||
|
// // begin
|
||
|
// o := TObject(lua_topointer(L, - 2));
|
||
|
// // lua_pop(L, 1);
|
||
|
// // end
|
||
|
// // else
|
||
|
// // begin
|
||
|
// // luaL_error(L, PAnsiChar('Type mismatch, expected LightUserData'));
|
||
|
// // Exit;
|
||
|
// // end;
|
||
|
//
|
||
|
// C := o as TWebContext;
|
||
|
// TStringBuilder(C.ReservedData).Append(s);
|
||
|
// Result := 0;
|
||
|
// end;
|
||
|
|
||
|
{ TMVCEmbeddedLuaView }
|
||
|
|
||
|
procedure TMVCLuaViewEngine.Execute(const ViewName: string;
|
||
|
const OutputStream: TStream);
|
||
|
var
|
||
|
Lua: TLuaEngine;
|
||
|
k: string;
|
||
|
LuaFilter: TLuaEmbeddedTextFilter;
|
||
|
lViewFileName: String;
|
||
|
_FFileName, CompiledFileName: string;
|
||
|
v: string;
|
||
|
sr: TStreamReader;
|
||
|
DecodeJSONStrings: string;
|
||
|
LuaRequestFunctions: TDictionary<string, lua_CFunction>;
|
||
|
LuaResponseFunctions: TDictionary<string, lua_CFunction>;
|
||
|
pn: string;
|
||
|
lSer: TMVCJsonDataObjectsSerializer;
|
||
|
lJSONStr: string;
|
||
|
lJSONObj: TJDOJsonObject;
|
||
|
lJSONArr: TJDOJsonArray;
|
||
|
LuaResponseData: TDictionary<string, TValue>;
|
||
|
ScriptOutputStringBuilder: TStreamWriter;
|
||
|
begin
|
||
|
ScriptOutputStringBuilder := TStreamWriter.Create(OutputStream, TEncoding.UTF8);
|
||
|
try
|
||
|
Lua := TLuaEngine.Create;
|
||
|
try
|
||
|
Lua.LoadExternalLibraries(TLuaDataSetExposerLibraries.Create);
|
||
|
|
||
|
lViewFileName := StringReplace(ViewName, '/', '\', [rfReplaceAll]);
|
||
|
// $0.02 of normalization
|
||
|
if lViewFileName = '\' then
|
||
|
lViewFileName := '\index.' + DEFAULT_VIEW_EXT
|
||
|
else
|
||
|
lViewFileName := lViewFileName + '.' + DEFAULT_VIEW_EXT;
|
||
|
|
||
|
if DirectoryExists(Config.Value[TMVCConfigKey.ViewPath]) then
|
||
|
_FFileName := ExpandFileName
|
||
|
(IncludeTrailingPathDelimiter(Config.Value[TMVCConfigKey.ViewPath]) +
|
||
|
lViewFileName)
|
||
|
else
|
||
|
_FFileName := ExpandFileName
|
||
|
(IncludeTrailingPathDelimiter(GetApplicationFileNamePath +
|
||
|
Config.Value[TMVCConfigKey.ViewPath]) + lViewFileName);
|
||
|
lViewFileName := _FFileName;
|
||
|
|
||
|
if not FileExists(lViewFileName) then
|
||
|
raise EMVCViewError.CreateFmt('View [%s.%s] not found',
|
||
|
[ViewName, DEFAULT_VIEW_EXT])
|
||
|
else
|
||
|
begin
|
||
|
DecodeJSONStrings := '';
|
||
|
{ continuare da questo problema }
|
||
|
if Assigned(ViewModel) then
|
||
|
begin
|
||
|
for k in ViewModel.Keys do
|
||
|
begin
|
||
|
if not(ViewModel[k] is TJDOJsonBaseObject) then
|
||
|
begin
|
||
|
lSer := TMVCJsonDataObjectsSerializer.Create(nil);
|
||
|
try
|
||
|
if TDuckTypedList.CanBeWrappedAsList(ViewModel[k]) then
|
||
|
begin
|
||
|
//List
|
||
|
lJSONArr := TJDOJsonArray.Create;
|
||
|
try
|
||
|
lSer.ListToJsonArray(TDuckTypedList.Wrap(ViewModel[k],
|
||
|
False), lJSONArr,
|
||
|
TMVCSerializationType.stDefault, [], nil);
|
||
|
lJSONStr := lJSONArr.ToJSON(True);
|
||
|
finally
|
||
|
lJSONArr.Free;
|
||
|
end;
|
||
|
end
|
||
|
else if ViewModel[k] is System.JSON.TJSONValue then
|
||
|
begin
|
||
|
//System.JSON
|
||
|
lJSONStr := System.JSON.TJSONValue(ViewModel[k]).ToJSON;
|
||
|
end
|
||
|
else
|
||
|
begin //PODO
|
||
|
lJSONObj := TJDOJsonObject.Create;
|
||
|
try
|
||
|
lSer.ObjectToJSONObject(ViewModel[k], lJSONObj,
|
||
|
TMVCSerializationType.stDefault, []);
|
||
|
lJSONStr := lJSONObj.ToJSON(True);
|
||
|
finally
|
||
|
lJSONObj.Free;
|
||
|
end;
|
||
|
end;
|
||
|
finally
|
||
|
lSer.Free;
|
||
|
end;
|
||
|
end
|
||
|
else
|
||
|
begin
|
||
|
//JsonDataObjects
|
||
|
lJSONStr := TJDOJsonBaseObject(ViewModel[k]).ToJSON(True);
|
||
|
end;
|
||
|
Lua.DeclareGlobalString(k, lJSONStr);
|
||
|
DecodeJSONStrings := DecodeJSONStrings + sLineBreak + 'local ' +
|
||
|
AnsiString(k) + ' = json.decode(' + AnsiString(k) + ')';
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
if Assigned(ViewDataSets) then
|
||
|
begin
|
||
|
for k in ViewDataSets.Keys do
|
||
|
begin
|
||
|
ExposeDataSet(Lua, ViewDataSets[k], ViewDataSets[k].Name);
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
Lua.DeclareGlobalString('__ROOT__', ExtractFilePath(ParamStr(0)));
|
||
|
Lua.DeclareGlobalString('__log_file', LOG_FILE_NAME);
|
||
|
Lua.DeclareGlobalLightUserData('__webcontext', WebContext);
|
||
|
Lua.DeclareGlobalLightUserData('__stringbuilder', ScriptOutputStringBuilder);
|
||
|
CompiledFileName := GetCompiledFileName(lViewFileName);
|
||
|
|
||
|
TMonitor.Enter(Self);
|
||
|
try
|
||
|
if true or (not IsCompiledVersionUpToDate(lViewFileName, CompiledFileName)) then
|
||
|
begin
|
||
|
LuaFilter := TLuaEmbeddedTextFilter.Create;
|
||
|
try
|
||
|
LuaFilter.OutputFunction := 'elua_out';
|
||
|
LuaFilter.TemplateCode := TFile.ReadAllText(lViewFileName,
|
||
|
TEncoding.ANSI);
|
||
|
LuaFilter.Execute;
|
||
|
TFile.WriteAllText(CompiledFileName,
|
||
|
LuaFilter.LuaCode,
|
||
|
TEncoding.ANSI);
|
||
|
finally
|
||
|
LuaFilter.Free;
|
||
|
end;
|
||
|
end;
|
||
|
finally
|
||
|
TMonitor.Exit(Self);
|
||
|
end;
|
||
|
|
||
|
// read lua compiled code
|
||
|
sr := TStreamReader.Create(TFileStream.Create(CompiledFileName,
|
||
|
fmOpenRead or fmShareDenyNone), TEncoding.UTF8);
|
||
|
try
|
||
|
sr.OwnStream;
|
||
|
v := sr.ReadToEnd;
|
||
|
finally
|
||
|
sr.Free;
|
||
|
end;
|
||
|
Lua.DeclareGlobalFunction('internal_elua_out', @__lua_stream_out);
|
||
|
Lua.LoadScript('require "Lua.boot" ' + sLineBreak + DecodeJSONStrings + sLineBreak + v);
|
||
|
// EXECUTE IT!!!
|
||
|
Lua.Execute;
|
||
|
finally
|
||
|
Lua.Free;
|
||
|
end;
|
||
|
finally
|
||
|
FreeAndNil(ScriptOutputStringBuilder);
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
function TMVCLuaViewEngine.GetCompiledFileName(const FileName: string): string;
|
||
|
var
|
||
|
CompiledFileDir: string;
|
||
|
CompiledFileName: string;
|
||
|
begin
|
||
|
// Exit(FileName + '.compiled');
|
||
|
CompiledFileDir := IncludeTrailingPathDelimiter(ExtractFilePath(FileName) +
|
||
|
'__compiled');
|
||
|
CompiledFileName := CompiledFileDir +
|
||
|
ChangeFileExt(ExtractFileName(FileName), '.lua');
|
||
|
ForceDirectories(CompiledFileDir);
|
||
|
Result := CompiledFileName;
|
||
|
end;
|
||
|
|
||
|
function TMVCLuaViewEngine.IsCompiledVersionUpToDate(const FileName,
|
||
|
CompiledFileName: string): Boolean;
|
||
|
var
|
||
|
dt1: TDateTime;
|
||
|
dt2: TDateTime;
|
||
|
begin
|
||
|
dt2 := 0;
|
||
|
FileAge(FileName, dt1);
|
||
|
FileAge(CompiledFileName, dt2);
|
||
|
Result := dt1 < dt2;
|
||
|
end;
|
||
|
|
||
|
end.
|