support for delphi 11.1

This commit is contained in:
Razor12911
2022-05-13 13:05:10 +02:00
parent 8ceccef928
commit 39fb5ae479
167 changed files with 8914 additions and 3205 deletions

View File

@@ -14,51 +14,42 @@ object MainLogView: TMainLogView
OnCreate = FormCreate
OnKeyDown = FormKeyDown
OnShow = FormShow
LCLVersion = '2.0.8.0'
object Splitter2: TSplitter
Cursor = crVSplit
Left = 0
Height = 4
Top = 635
Width = 860
Align = alBottom
ResizeAnchor = akBottom
end
LCLVersion = '2.0.11.0'
object Splitter3: TSplitter
Left = 837
Height = 635
Height = 583
Top = 0
Width = 4
Visible = False
end
object Splitter1: TSplitter
Left = 829
Height = 635
Height = 583
Top = 0
Width = 4
Visible = False
end
object Splitter4: TSplitter
Left = 833
Height = 635
Height = 583
Top = 0
Width = 4
Visible = False
end
object PanelLeft: TPanel
Left = 257
Height = 635
Height = 583
Top = 0
Width = 150
Align = alLeft
ClientHeight = 635
ClientHeight = 583
ClientWidth = 150
Constraints.MinWidth = 150
TabOrder = 0
object ImageLogo: TImage
Left = 8
Height = 32
Top = 591
Top = 539
Width = 137
Anchors = [akLeft, akRight, akBottom]
Center = True
@@ -421,9 +412,27 @@ object MainLogView: TMainLogView
TopIndex = -1
end
end
object Splitter2: TSplitter
Cursor = crVSplit
Left = 0
Height = 4
Top = 583
Width = 860
Align = alBottom
ResizeAnchor = akBottom
end
object PanelBottom: TPanel
Left = 0
Height = 52
Top = 587
Width = 860
Align = alBottom
TabOrder = 9
OnResize = PanelBottomResize
end
object List: TDrawGrid
Left = 841
Height = 635
Height = 583
Top = 0
Width = 19
Align = alClient
@@ -447,7 +456,7 @@ object MainLogView: TMainLogView
end
object ProfileList: TDrawGrid
Left = 407
Height = 635
Height = 583
Top = 0
Width = 274
Align = alLeft
@@ -468,17 +477,17 @@ object MainLogView: TMainLogView
end
object PanelThread: TPanel
Left = 681
Height = 635
Height = 583
Top = 0
Width = 148
Align = alLeft
ClientHeight = 635
ClientHeight = 583
ClientWidth = 148
TabOrder = 3
Visible = False
object ThreadListBox: TCheckListBox
Left = 1
Height = 593
Height = 541
Top = 1
Width = 146
Align = alClient
@@ -492,7 +501,7 @@ object MainLogView: TMainLogView
object pnlThreadBottom: TPanel
Left = 1
Height = 40
Top = 594
Top = 542
Width = 146
Align = alBottom
ClientHeight = 40
@@ -512,11 +521,11 @@ object MainLogView: TMainLogView
end
object PanelBrowse: TPanel
Left = 0
Height = 635
Height = 583
Top = 0
Width = 257
Align = alLeft
ClientHeight = 635
ClientHeight = 583
ClientWidth = 257
Constraints.MinWidth = 80
TabOrder = 4

View File

@@ -57,6 +57,7 @@ type
fInts: TIntegerDynArray;
fCreateTime: TCreateTime;
fData: TSQLRawBlob;
fFP: double;
published
property Name: RawUTF8 read fName write fName stored AS_UNIQUE;
property Age: integer read fAge write fAge;
@@ -65,6 +66,7 @@ type
property Ints: TIntegerDynArray index 1 read fInts write fInts;
property Data: TSQLRawBlob read fData write fData;
property CreateTime: TCreateTime read fCreateTime write fCreateTime;
property FP: double read fFP write fFP;
end;
TTestORM = class(TSynTestCase)
@@ -183,8 +185,9 @@ begin
Check(serverTime<>0);
CheckSame(Now,serverTime,0.5);
if System.Pos('MongoDB',Owner.CustomVersions)=0 then
Owner.CustomVersions := Owner.CustomVersions+'Using '+
string(fClient.ServerBuildInfoText);
Owner.CustomVersions := format('%sUsing %s'#13#10'Running on %s'#13#10+
'Compiled with mORMot '+SYNOPSE_FRAMEWORK_VERSION,
[Owner.CustomVersions,fClient.ServerBuildInfoText,OSVersionText]);
fExpectedCount := COLL_COUNT;
end;
@@ -452,6 +455,7 @@ begin
R.Value := _ObjFast(['num',i]);
R.Ints := nil;
R.DynArray(1).Add(i);
R.FP := i*7.3445;
Check(fClient.BatchAdd(R,True)>=0);
end;
finally
@@ -473,6 +477,7 @@ begin
Check(Length(R.Ints)=1);
Check(R.Ints[0]=aID);
Check(R.CreateTime>=fStartTimeStamp);
CheckSame(R.FP,aID*7.3445);
end;
procedure TTestORM.Retrieve;

View File

@@ -19,12 +19,14 @@ type
fTitle: RawUTF8;
fLanguage: RawUTF8;
fAbout: RawUTF8;
fLink: RawUTF8;
published
property Title: RawUTF8 index 80 read fTitle write fTitle;
property Language: RawUTF8 index 3 read fLanguage write fLanguage;
property Description: RawUTF8 index 120 read fDescription write fDescription;
property Copyright: RawUTF8 index 80 read fCopyright write fCopyright;
property About: RawUTF8 read fAbout write fAbout;
property Link: RawUTF8 index 60 read fLink write fLink;
end;
TSQLRecordTimeStamped = class(TSQLRecord)
@@ -46,6 +48,7 @@ type
fHashedPassword: RawUTF8;
fLogonName: RawUTF8;
public
function ComputeHash(const PlainPassword: RawUTF8): RawUTF8; virtual;
procedure SetPlainPassword(const PlainPassword: RawUTF8);
function CheckPlainPassword(const PlainPassword: RawUTF8): boolean;
function Name: RawUTF8;
@@ -96,6 +99,7 @@ type
OrderID: TIntegerDynArray;
procedure Init(aRest: TSQLRest);
function Get(tagID: integer): RawUTF8;
function GetIDFromIdent(const Ident: RawUTF8): integer;
procedure SaveOccurence(aRest: TSQLRest);
procedure SortTagsByIdent(var Tags: TIntegerDynArray);
function GetAsDocVariantArray: Variant;
@@ -106,6 +110,7 @@ type
fAbstract: RawUTF8;
fPublishedMonth: Integer;
fTags: TIntegerDynArray;
fLegacyHash: Int64;
public
class function CurrentPublishedMonth: Integer;
class procedure InitializeTable(Server: TSQLRestServer; const FieldName: RawUTF8;
@@ -118,6 +123,8 @@ type
property Abstract: RawUTF8 read fAbstract write fAbstract;
// "index 1" below to allow writing e.g. aArticle.DynArray(1).Delete(aIndex)
property Tags: TIntegerDynArray index 1 read fTags write fTags;
// xxhash32 of legacy post_url
property LegacyHash: Int64 read fLegacyHash write fLegacyHash;
end;
TSQLArticleSearch = class(TSQLRecordFTS4Porter)
@@ -157,6 +164,8 @@ procedure DotClearFlatImport(Rest: TSQLRest; const aFlatFile: RawUTF8;
var aTagsLookup: TSQLTags; const aDotClearRoot: RawUTF8;
const aStaticFolder: TFileName);
function ComputeLegacyHash(url: PUTF8Char): cardinal;
implementation
@@ -176,12 +185,16 @@ end;
{ TSQLSomeone }
const
SALT = 'mORMot';
function TSQLSomeone.ComputeHash(const PlainPassword: RawUTF8): RawUTF8;
var dig: THash256;
begin
PBKDF2_SHA3(SHA3_224,PlainPassword,LogonName+'@mORMot',30,@dig);
BinToHexLower(@dig,28,result);
end;
function TSQLSomeone.CheckPlainPassword(const PlainPassword: RawUTF8): boolean;
begin
result := fHashedPassword=SHA256(SALT+LogonName+PlainPassword);
result := fHashedPassword=ComputeHash(PlainPassword);
end;
function TSQLSomeone.Name: RawUTF8;
@@ -191,7 +204,7 @@ end;
procedure TSQLSomeone.SetPlainPassword(const PlainPassword: RawUTF8);
begin
fHashedPassword := SHA256(SALT+LogonName+PlainPassword);
fHashedPassword := ComputeHash(PlainPassword);
end;
@@ -233,6 +246,8 @@ begin
inherited;
if (FieldName='') or (FieldName='PublishedMonth') then
Server.CreateSQLIndex(TSQLArticle,'PublishedMonth',false);
if (FieldName='') or (FieldName='LegacyHash') then
Server.CreateSQLIndex(TSQLArticle,'LegacyHash',false);
end;
procedure TSQLArticle.SetPublishedMonth(FromTime: TTimeLog);
@@ -260,8 +275,20 @@ begin
result := '';
end;
function TSQLTags.GetIDFromIdent(const Ident: RawUTF8): integer;
var i: PtrInt;
begin
if Ident<>'' then
for i := 0 to length(Lookup)-1 do
if IdemPropNameU(Lookup[i].Ident,Ident) then begin
result := i+1;
exit;
end;
result := 0;
end;
function TSQLTags.GetAsDocVariantArray: Variant;
var i,ndx: Integer;
var i,ndx: PtrInt;
begin
TDocVariant.NewFast(result);
with Lock.ProtectMethod do
@@ -438,21 +465,46 @@ begin
until P=nil;
end;
function HttpGet(const aURI: SockString; outHeaders: PSockString=nil;
forceNotSocket: boolean=false; outStatus: PInteger=nil): SockString;
begin
result := '';
if outStatus<>nil then
outStatus^ := 404;
end;
function ComputeLegacyHash(url: PUTF8Char): cardinal;
var c: ansichar;
begin
result := 0;
if url<>nil then
repeat
case url^ of
#0: exit;
'a'..'z', 'A'..'Z', '0'..'9': begin
c := upcase(url^);
result := crc32c(result, @c, 1);
end;
end;
inc(url);
until false;
end;
procedure DotClearFlatImport(Rest: TSQLRest; const aFlatFile: RawUTF8;
var aTagsLookup: TSQLTags; const aDotClearRoot: RawUTF8;
const aStaticFolder: TFileName);
var T,tagTable,postTable: TDotClearTable;
data,urls: TRawUTF8List;
data: TRawUTF8List;
urls: TIntegerDynArray;
info: TSQLBlogInfo;
article: TSQLArticle;
comment: TSQLComment;
tag: TSQLTag;
tags: TRawUTF8DynArray;
tags, notfound: TRawUTF8DynArray;
tagID: TIDDynArray;
tagsCount: integer;
batch: TSQLRestBatch;
PublicFolder: TFileName;
notfound: TRawUTF8DynArray;
r,ndx,post_url,meta_id,meta_type,tag_post_id,postID,post_id: integer;
function FixLinks(P: PUTF8Char): RawUTF8;
@@ -501,13 +553,20 @@ var T,tagTable,postTable: TDotClearTable;
continue;
AddNoJSONEscape(B,H-B);
P := H;
if IdemPChar(P,'HTTP://BLOG.SYNOPSE.INFO/') then
inc(P,24)
else if IdemPChar(P,'HTTPS://BLOG.SYNOPSE.INFO/') then
inc(P,25);
if IdemPChar(P,'HTTP://SYNOPSE.INFO') then begin
AddShort('https://synopse.info');
inc(P,19);
end else if P^='/' then begin
if P[1]='?' then
inc(P);
if IdemPChar(P+1,'POST/') then begin
GetUrl(P+6);
i := urls.IndexOf(urlnoparam);
i := IntegerScanIndex(pointer(urls),length(urls),
ComputeLegacyHash(pointer(urlnoparam)));
if i>=0 then begin
AddShort('articleView?id=');
Add(i+1);
@@ -582,7 +641,6 @@ begin
end;
auto1 := TAutoFree.Several([
@data,TDotClearTable.Parse(aFlatFile),
@urls,TRawUTF8ListHashed.Create,
@batch,TSQLRestBatch.Create(Rest,TSQLTag,5000)]);
auto2 := TSQLRecord.AutoFree([ // avoid several try..finally
@info,TSQLBlogInfo, @article,TSQLArticle, @comment,TSQLComment, @tag,TSQLTag]);
@@ -614,7 +672,7 @@ begin
post_url := postTable.FieldIndexExisting('post_url');
if postTable.Step(true) then
repeat
urls.Add(postTable.FieldBuffer(post_url));
AddInteger(urls,ComputeLegacyHash(postTable.FieldBuffer(post_url)));
until not postTable.Step;
article.Author := TSQLAuthor(1);
article.AuthorName := 'synopse';
@@ -631,6 +689,7 @@ begin
article.ModifiedAt := Iso8601ToTimeLog(postTable.GetU(r,'post_upddt'));
article.SetPublishedMonth(article.CreatedAt);
postID := postTable.GetAsInteger(r,post_id);
article.LegacyHash := ComputeLegacyHash(postTable.Get(r,post_url));
article.Tags := nil;
if tagTable.Step(true) then
repeat

View File

@@ -38,17 +38,25 @@ var aModel: TSQLModel;
aApplication: TBlogApplication;
aHTTPServer: TSQLHttpServer;
begin
//with TSQLLog.Family do Level := LOG_VERBOSE;
with TSQLLog.Family do begin
Level := LOG_VERBOSE;
PerThreadLog := ptIdentifiedInOnFile;
RotateFileCount := 10;
RotateFileSizeKB := 20 shl 10;
FileExistsAction := acAppend; // as expected by rotation
end;
aModel := CreateModel;
try
aServer := TSQLRestServerDB.Create(aModel,ChangeFileExt(ExeVersion.ProgramFileName,'.db'));
try
aServer.DB.Synchronous := smNormal;
aServer.DB.LockingMode := lmExclusive;
aServer.Options := aServer.Options+[rsoNoTableURI];
aServer.CreateMissingTables;
aApplication := TBlogApplication.Create;
try
aApplication.Start(aServer);
aServer.ServiceMethodRegisterPublishedMethods('', aApplication);
aHTTPServer := TSQLHttpServer.Create('8092',aServer
{$ifndef ONLYUSEHTTPSOCKET},'+',useHttpApiRegisteringURI{$endif});
try

View File

@@ -12,6 +12,7 @@ uses
SynCommons,
SynLog,
SynTests,
SynCrtSock,
mORMot,
mORMotMVC,
MVCModel;
@@ -31,8 +32,9 @@ type
out Comments: TObjectList);
procedure AuthorView(
var ID: TID; out Author: TSQLAuthor; out Articles: variant);
function Login(
const LogonName,PlainPassword: RawUTF8): TMVCAction;
procedure LoginView;
function Login(const LogonName,PlainPassword,
NewPlainPassword1,NewPlainPassword2: RawUTF8): TMVCAction;
function Logout: TMVCAction;
function ArticleComment(ID: TID; const Title,Comment: RawUTF8): TMVCAction;
function ArticleMatch(const Match: RawUTF8): TMVCAction;
@@ -71,7 +73,13 @@ type
procedure TagToText(const Value: variant; out result: variant);
public
procedure Start(aServer: TSQLRestServer); reintroduce;
published
// low-level blog/post blog/tag blog/rss endpoints
procedure Post(Ctxt: TSQLRestServerURIContext);
procedure Tag(Ctxt: TSQLRestServerURIContext);
procedure Rss(Ctxt: TSQLRestServerURIContext);
public
// IBlogApplication implemented methods
procedure Default(var Scope: variant);
procedure ArticleView(ID: TID;
var WithComments: boolean; Direction: integer; var Scope: variant;
@@ -79,7 +87,9 @@ type
out Comments: TObjectList);
procedure AuthorView(
var ID: TID; out Author: TSQLAuthor; out Articles: variant);
function Login(const LogonName,PlainPassword: RawUTF8): TMVCAction;
procedure LoginView;
function Login(const LogonName,PlainPassword,
NewPlainPassword1,NewPlainPassword2: RawUTF8): TMVCAction;
function Logout: TMVCAction;
function ArticleComment(ID: TID; const Title,Comment: RawUTF8): TMVCAction;
function ArticleMatch(const Match: RawUTF8): TMVCAction;
@@ -130,6 +140,10 @@ begin
finally
Free;
end;
_Safe(fBlogMainInfo)^.AddValue('engine',RawUTF8ToVariant(
'Website powered by mORMot MVC '+SYNOPSE_FRAMEWORK_VERSION+
', compiled with '+GetDelphiCompilerVersion+
', running on '+RawUTF8(ToText(OSVersion32))+'.'));
end;
procedure TBlogApplication.MonthToText(const Value: variant;
@@ -172,29 +186,28 @@ begin
auto := TSQLRecord.AutoFree([ // avoid several try..finally
@info,TSQLBlogInfo, @article,TSQLArticle, @comment,TSQLComment, @tag,TSQLTag]);
if not RestModel.Retrieve('',info) then begin // retrieve first item
tmp := StringFromFile('/home/ab/Downloads/2020-06-16-a8003957c2ae6bde5be6ea279c9c9ce4-backup.txt');
tmp := StringFromFile(ExeVersion.ProgramFilePath+'2021-01-20-16-37-default-backup.txt');
info.Language := 'en';
if tmp<>'' then begin
info.Title := 'Synopse Blog';
info.Description := 'Articles, announcements, news, updates and more '+
'about our Open Source projects';
'about Synopse Open Source projects';
info.About := 'Latest information about Synopse Open Source librairies, '+
'mainly the mORMot ORM/SOA/MVC framework, and SynPDF.';
info.Link := 'https://blog.synopse.info';
end else begin
info.Title := 'mORMot BLOG';
info.Description := 'Sample Blog Web Application using Synopse mORMot MVC';
info.About := TSynTestCase.RandomTextParagraph(10,'!');
info.Link := 'http://localhost:8092';
end;
info.About := info.About+#13#10'Website powered by mORMot MVC '+
SYNOPSE_FRAMEWORK_VERSION+', compiled with '+GetDelphiCompilerVersion+
', running on '+ToText(OSVersion32)+'.';
info.Copyright := '&copy;'+ToUTF8(CurrentYear)+'<a href=https://synopse.info>Synopse Informatique</a>';
RestModel.Add(info,true);
end;
if RestModel.TableHasRows(TSQLArticle) then
exit;
if tmp<>'' then begin
DotClearFlatImport(RestModel,tmp,fTagsLookup,'http://blog.synopse.info',
DotClearFlatImport(RestModel,tmp,fTagsLookup,'https://blog.synopse.info',
(TMVCRunOnRestServer(fMainRunner).Views as TMVCViewsMustache).ViewStaticFolder);
exit;
end;
@@ -271,20 +284,24 @@ begin
fDefaultData.AddNewProp('tags',fTagsLookup.GetAsDocVariantArray,info);
end;
procedure TBlogApplication.FlushAnyCache;
begin
inherited FlushAnyCache; // call fMainRunner.NotifyContentChanged
fDefaultData.Clear;
end;
{ TBlogApplication - Commands }
const
ARTICLE_FIELDS = 'RowID,Title,Tags,Abstract,ContentHtml,Author,AuthorName,CreatedAt';
ARTICLE_DEFAULT_LIMIT = ' limit 20';
ARTICLE_DEFAULT_ORDER: RawUTF8 = 'order by RowID desc'+ARTICLE_DEFAULT_LIMIT;
procedure TBlogApplication.FlushAnyCache;
begin
inherited FlushAnyCache; // call fMainRunner.NotifyContentChanged
fDefaultData.Clear;
// get last 20 articles
fDefaultData.SetValue('Articles',
RestModel.RetrieveDocVariantArray(TSQLArticle,'',
ARTICLE_DEFAULT_ORDER,[],ARTICLE_FIELDS,nil,@fDefaultLastID));
end;
{ TBlogApplication - Commands }
procedure TBlogApplication.Default(var Scope: variant);
var scop: PDocVariantData;
lastID: TID;
@@ -320,14 +337,12 @@ begin
whereClause := whereClause+' and IntegerDynArrayContains(Tags,?)';
end;
SetVariantNull(Scope);
if (lastID=0) and (tag=0) then begin // use simple cache if no parameters
if not fDefaultData.AddExistingProp('Articles',Scope) then begin
articles := RestModel.RetrieveDocVariantArray(TSQLArticle,'',
ARTICLE_DEFAULT_ORDER,[],ARTICLE_FIELDS,nil,@fDefaultLastID);
fDefaultData.AddNewProp('Articles',articles,Scope);
end;
if (lastID=0) and (tag=0) then begin
// use simple cache if no parameters
fDefaultData.AddExistingProp('Articles',Scope); // set by FlushAnyCache
lastID := fDefaultLastID;
end else begin // use more complex request using lastID + tag parameters
end else begin
// use more complex request using lastID + tag parameters
articles := RestModel.RetrieveDocVariantArray(TSQLArticle,'',
whereClause+ARTICLE_DEFAULT_ORDER,[lastID,tag],ARTICLE_FIELDS,nil,@lastID);
scope := _ObjFast(['Articles',articles]);
@@ -370,12 +385,31 @@ begin
raise EMVCApplication.CreateGotoError(HTTP_NOTFOUND);
end;
function TBlogApplication.Login(const LogonName, PlainPassword: RawUTF8): TMVCAction;
procedure TBlogApplication.LoginView;
begin
end;
function TBlogApplication.Login(const LogonName, PlainPassword,
NewPlainPassword1, NewPlainPassword2: RawUTF8): TMVCAction;
var Author: TSQLAuthor;
SessionInfo: TCookieData;
newpwd: RawUTF8;
begin
if CurrentSession.CheckAndRetrieve<>0 then begin
GotoError(result,HTTP_BADREQUEST);
if LogonName='' then begin
GotoView(result,'LoginView',[]);
exit;
end;
newpwd := Trim(NewPlainPassword1);
if newpwd<>'' then begin
if (newpwd<>NewPlainPassword2) or
(newpwd=PlainPassword) or
(CurrentSession.CheckAndRetrieve(@SessionInfo,TypeInfo(TCookieData))=0) or
(SessionInfo.AuthorName<>LogonName) then begin
GotoError(result,HTTP_NOTACCEPTABLE);
exit;
end;
end else if CurrentSession.CheckAndRetrieve<>0 then begin
GotoError(result,'Already Logged In',HTTP_BADREQUEST);
exit;
end;
Author := TSQLAuthor.Create(RestModel,'LogonName=?',[LogonName]);
@@ -385,6 +419,10 @@ begin
SessionInfo.AuthorID := Author.ID;
SessionInfo.AuthorRights := Author.Rights;
CurrentSession.Initialize(@SessionInfo,TypeInfo(TCookieData));
if newpwd<>'' then begin
Author.SetPlainPassword(newpwd);
RestModel.Update(Author,'HashedPassword');
end;
GotoDefault(result);
end else
GotoError(result,sErrorInvalidLogin);
@@ -481,6 +519,83 @@ begin
end;
end;
procedure TBlogApplication.Post(Ctxt: TSQLRestServerURIContext);
var hash, id: Int64;
begin
hash := ComputeLegacyHash(pointer(UrlDecode(Ctxt.URIAfterRoot,5,-1)));
id := RestModel.OneFieldValueInt64(TSQLArticle,'ID',
FormatUTF8('LegacyHash=:(%):', [hash]));
Ctxt.Redirect(FormatUTF8('/%/articleview?id=%',[RestModel.Model.Root,id]));
end;
procedure TBlogApplication.Tag(Ctxt: TSQLRestServerURIContext);
var
id: integer;
begin
id := fTagsLookup.GetIDFromIdent(copy(Ctxt.UriAfterRoot, 5, 100));
Ctxt.Redirect(FormatUTF8('/%/default?scope={tag:%}',[RestModel.Model.Root,id]));
end;
function Esc(const Msg: RawUTF8): RawUTF8;
var i: integer;
ins: RawUTF8;
begin
// fast enough for our purpose to compute some RSS cache
result := Msg;
for i := length(Msg) downto 1 do begin
case Msg[i] of
'"': ins := '&quot';
'&': ins := '&amp';
'<': ins := '&lt';
'>': ins := '&gt';
else Continue;
end;
result[i] := ';';
insert(ins,result,i);
end;
end;
procedure TBlogApplication.Rss(Ctxt: TSQLRestServerURIContext);
function ComputeRss: variant;
var xml, lng, link: RawUTF8;
art: integer;
begin
with _Safe(fBlogMainInfo)^ do
begin
link := U['Link'];
if (link<>'') and (link[length(link)]='/') then
SetLength(link,length(link)-1);
lng := U['Language'];
if lng='' then
lng := 'en_US';
FormatUTF8('<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">'+
'<channel><title>%</title>'+
'<link>%</link><description>%</description>'+
'<lastBuildDate>%</lastBuildDate><language>%</language>',
[Esc(U['Title']),link,Esc(U['Description']),
DateTimeToHTTPDate(NowUTC,'+0000'),lng],xml);
end;
with _Safe(fDefaultData.GetValue('Articles'))^ do
for art := 0 to Count-1 do
with _Safe(Values[art])^ do
xml := FormatUTF8('%'#13'<item><title>%</title>'+
'<link>%/articleview?id=%</link><pubDate>%</pubDate><category>blog</category>'+
'<dc:creator>%</dc:creator><description><![CDATA[%]]></description>'+
'<content:encoded><![CDATA[%]]></content:encoded></item>',
[xml,Esc(U['Title']),link,I['ID'],
DateTimeToHTTPDate(TimeLogToDateTime(I['CreatedAt']),'+0000'),
Esc(U['AuthorName']),U['Abstract'],U['Content']]);
RawUTF8ToVariant(xml+'</channel></rss>',result);
end;
var
rss: variant;
begin
if not fDefaultData.ExistsOrLock('rss',rss) then
fDefaultData.ReplaceAndUnlock('rss',ComputeRss,rss);
Ctxt.Returns(ToUTF8(rss),HTTP_SUCCESS,
HEADER_CONTENT_TYPE+'application/rss+xml; charset=UTF-8',{handle304=}true);
end;
initialization
{$ifndef DELPHI2010}
// manual definition mandatory only if Delphi 2010 RTTI is not available

View File

@@ -1,51 +1,51 @@
{{>header}}
{{>masthead}}
<div class="blog-header">
<h1 class="blog-title">{{article.title}}</h1>
<div class="lead blog-description">
Written by <a href="authorView?id={{article.Author}}">{{article.AuthorName}}</a> ({{author.FirstName}} {{author.FamilyName}}) on {{TimeLogToText article.CreatedAt}}<br />
{{#article.tags}}<a href="default?scope={tag:{{.}}}" class="label label-info">{{TagToText .}}</a> {{/article.tags}}
</div>
</div>
<div class="row">
<div class="col-sm-8 blog-main">
{{#article}}
{{#ContentHtml}}{{{abstract}}}{{/ContentHtml}}{{^ContentHtml}}{{{WikiToHtml abstract}}}{{/ContentHtml}}
<hr>
{{#ContentHtml}}{{{content}}}{{/ContentHtml}}{{^ContentHtml}}{{{WikiToHtml content}}}{{/ContentHtml}}
{{/article}}
<hr>
<ul class="pager">
<li class="previous"><a href="ArticleView?id={{Article.ID}}&withComments={{withComments}}&direction=1">&larr; Previous</a></li>
<li class="next"><a href="ArticleView?id={{Article.ID}}&withComments={{withComments}}&direction=2">Next &rarr;</a></li>
</ul>
<a name="comments"></a>
{{#WithComments}}
{{#Comments}}
<blockquote>
<p><strong>{{Title}}</strong></p>
<p>{{#ContentHtml}}{{{Content}}}{{/ContentHtml}}{{^ContentHtml}}{{{WikiToHtml Content}}}{{/ContentHtml}}</p>
<footer>Commented on {{TimeLogToText CreatedAt}} by <a href="authorView?id={{Author}}">{{AuthorName}}</a></<footer>
</blockquote>
<hr>
{{/Comments}}
{{^Comments}}<blockquote>No comment yet.</blockquote>{{/Comments}}
<p><a href="ArticleView?id={{Article.ID}}#comments" class="btn btn-primary btn-sm">Hide Comments</a></p>
{{#main.session.AuthorRights.Comment}}
<a name="addComment"></a>
<form class="form-horizontal" action="ArticleComment#addComment" method="post">
<div class="form-group">
{{#Scope}}<div class="alert alert-danger">{{CommentError}}</div>{{/Scope}}
<input type="hidden" name="id" value={{Article.ID}}>
<input type="text" class="form-control" name="title" placeholder="Title" value="{{Scope.CommentTitle}}">
<textarea class="form-control" name="comment" rows="7" placeholder="Enter a new comment here">{{Scope.CommentContent}}</textarea>
<button type="submit" class="btn btn-primary btn-sm">Add Comment</a>
</div>
</form>
{{/main.session.AuthorRights.Comment}}
</p>
{{/WithComments}}
{{^WithComments}}
<p><a href="ArticleView?id={{Article.ID}}&withComments=true#comments" class="btn btn-primary btn-sm">Show Comments</a></p>
{{/WithComments}}
<div class="blog-header">
<h1 class="blog-title">{{article.title}}</h1>
<div class="lead blog-description">
Written by <a href="authorView?id={{article.Author}}">{{article.AuthorName}}</a> ({{author.FirstName}} {{author.FamilyName}}) on {{TimeLogToText article.CreatedAt}}<br />
{{#article.tags}}<a href="default?scope={tag:{{.}}}" class="label label-info">{{TagToText .}}</a> {{/article.tags}}
</div>
</div>
<div class="row">
<div class="col-sm-8 blog-main">
{{#article}}
{{#ContentHtml}}{{{abstract}}}{{/ContentHtml}}{{^ContentHtml}}{{{WikiToHtml abstract}}}{{/ContentHtml}}
<hr>
{{#ContentHtml}}{{{content}}}{{/ContentHtml}}{{^ContentHtml}}{{{WikiToHtml content}}}{{/ContentHtml}}
{{/article}}
<hr>
<ul class="pager">
<li class="previous"><a href="ArticleView?id={{Article.ID}}&withComments={{withComments}}&direction=1">&larr; Previous</a></li>
<li class="next"><a href="ArticleView?id={{Article.ID}}&withComments={{withComments}}&direction=2">Next &rarr;</a></li>
</ul>
<a name="comments"></a>
{{#WithComments}}
{{#Comments}}
<blockquote>
<p><strong>{{Title}}</strong></p>
<p>{{#ContentHtml}}{{{Content}}}{{/ContentHtml}}{{^ContentHtml}}{{{WikiToHtml Content}}}{{/ContentHtml}}</p>
<footer>Commented on {{TimeLogToText CreatedAt}} by <a href="authorView?id={{Author}}">{{AuthorName}}</a></<footer>
</blockquote>
<hr>
{{/Comments}}
{{^Comments}}<blockquote>No comment yet.</blockquote>{{/Comments}}
<p><a href="ArticleView?id={{Article.ID}}#comments" class="btn btn-primary btn-sm">Hide Comments</a></p>
{{#main.session.AuthorRights.Comment}}
<a name="addComment"></a>
<form class="form-horizontal" action="ArticleComment#addComment" method="post">
<div class="form-group">
{{#Scope}}<div class="alert alert-danger">{{CommentError}}</div>{{/Scope}}
<input type="hidden" name="id" value={{Article.ID}}>
<input type="text" class="form-control" name="title" placeholder="Title" value="{{Scope.CommentTitle}}">
<textarea class="form-control" name="comment" rows="7" placeholder="Enter a new comment here">{{Scope.CommentContent}}</textarea>
<button type="submit" class="btn btn-primary btn-sm">Add Comment</a>
</div>
</form>
{{/main.session.AuthorRights.Comment}}
</p>
{{/WithComments}}
{{^WithComments}}
<p><a href="ArticleView?id={{Article.ID}}&withComments=true#comments" class="btn btn-primary btn-sm">Show Comments</a></p>
{{/WithComments}}
{{>footer}}

View File

@@ -1,15 +1,25 @@
{{>header}}
{{>masthead}}
<div class="blog-header">
<h1 class="blog-title">User {{Author.LogonName}}</h1>
<div class="lead blog-description">{{Author.FirstName}} {{Author.FamilyName}}
</div>
</div>
<div class="row">
<div class="col-sm-8 panel">
<div class="panel-heading">Information about <strong>{{Author.LogonName}}</strong></div>
<div class="panel-body">
{{{TSQLAuthor.HtmlTable Author}}}
</div>
<div class="blog-header">
<h1 class="blog-title">User {{Author.LogonName}}</h1>
<div class="lead blog-description">{{Author.FirstName}} {{Author.FamilyName}}
</div>
</div>
<div class="row">
<div class="col-sm-8 panel">
<div class="panel-heading">Information about <strong>{{Author.LogonName}}</strong></div>
<div class="panel-body">
{{{TSQLAuthor.HtmlTable Author}}}
{{#main.session}}
<form class="navbar-form" action="login" method="post">
<span class="navbar-text">Change <strong>{{AuthorName}}</strong> Password:</span>
<input type="text" class="form-control" name="LogonName" placeholder="Username">
<input type="password" class="form-control" name="PlainPassword" placeholder="Old Password">
<input type="password" class="form-control" name="NewPlainPassword1" placeholder="New Password">
<input type="password" class="form-control" name="NewPlainPassword2" placeholder="Repeat New Password">
<button class="btn" type="submit">Change</button>
</form>
{{/main.session}}
</div>
{{>articlerow}}
{{>footer}}

View File

@@ -22,8 +22,23 @@
</div><!-- container -->
<div class="blog-footer">
<p>Proudly using the Open Source <a href="http://mormot.net">mORMot ORM/SOA/MVC Framework</a>.<br>
{{{main.blog.engine}}}<br>
<small>{{{main.blog.copyright}}} - page generated in [[GENERATION_TIME_TAG]]</small></p>
<p><a href="#">Back to top</a></p>
<ul class="pager">
<li><a href="#">Back to top</a></li>
<li><a href=rss>RSS feed</a></li>
<li><a href=https://synopse.info/forum/viewtopic.php?id=25>Synopse</a></li>
<li><a href=https://synopse.info/forum>Support Forum</a></li>
<li><a href=https://github.com/synopse>On Github</a></li>
</ul>
{{#main.session}}
<p>
<form class="navbar-form" action="logout">
<span class="navbar-text">Signed in as <a href=AuthorView?id={{AuthorID}}>{{AuthorName}}</a></span>
<button class="btn" type="submit">Logout</button>
</form>
</p>
{{/main.session}}
</div>
</body>
</html>

View File

@@ -3,44 +3,23 @@
<div class="blog-masthead">
<div class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button class="navbar-toggle" type="button" data-toggle="collapse" data-target="#navbar-main">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<div class="navbar-left">
<a class="navbar-brand" href="default">{{main.blog.title}}</a>
</div>
<div class="navbar-form navbar-right">
<form action="articleMatch" method="post">
<div class="form-group">
<input type="text" class="form-control" name="match" placeholder="Search Expression">
</div>
</form>
</div>
</div>
<center>
<div class="navbar-collapse collapse" id="navbar-main">
{{#main.session}}
<div class="navbar-right">
<form class="navbar-form" action="logout">
<span class="navbar-text">Signed in as <a href=AuthorView?id={{AuthorID}}>{{AuthorName}}</a></span>
<button class="btn" type="submit">Logout</button>
</form>
</div>
{{/main.session}}
{{^main.session}}
<form class="navbar-form navbar-right" action="login" method="post">
<div class="form-group">
<input type="text" class="form-control" name="LogonName" placeholder="Username">
<input type="password" class="form-control" name="PlainPassword" placeholder="Password">
</div>
<button type="submit" class="btn btn-default">Sign In</button>
</form>
{{/main.session}}
</div>
</center>
<div class="navbar-header">
<button class="navbar-toggle" type="button" data-toggle="collapse" data-target="#navbar-main">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<div class="navbar-left">
<a class="navbar-brand" href="default">{{main.blog.title}}</a>
</div>
<div class="navbar-form navbar-right">
<form action="articleMatch" method="post">
<div class="form-group">
<input type="text" class="form-control" name="match" placeholder="Search Expression">
</div>
</form>
</div>
</div>
</div>
</div>
</div>

View File

@@ -8,6 +8,7 @@ uses
SysUtils,
Classes,
SynCommons,
SynTable,
mORMot,
mORMotHttpClient,
Project31ChatCallbackInterface in 'Project31ChatCallbackInterface.pas';

View File

@@ -3,7 +3,7 @@ unit ECCProcess;
(*
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2020 Arnaud Bouchez
Synopse framework. Copyright (C) 2022 Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
@@ -22,7 +22,7 @@ unit ECCProcess;
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (C) 2020
Portions created by the Initial Developer are Copyright (C) 2022
the Initial Developer. All Rights Reserved.
Contributor(s):

View File

@@ -28,7 +28,7 @@ uses
BaseUnix,
{$endif}
{$ifdef LINUXNOTBSD}
SynSystemd,
SynFPCLinux,
mORMotService,
{$endif}
mORMotHttpServer; // HTTP server for RESTful server
@@ -163,12 +163,12 @@ const
UNIX_SOCK_PATH = '/tmp/rest-bench.socket';
{$ifdef LINUX}
/// killa process after X second without GEt requests
/// killa process after X second without Get requests
function inactivityWatchdog(p: pointer): ptrint;
var currentRC: TSynMonitorCount64;
begin
repeat
sleep(10000); /// once per 10 second
sleep(10000); // once per 10 second
if aRestServer = nil then // not initialized
continue;
currentRC := aRestServer.Stats.Read;
@@ -182,6 +182,7 @@ begin
Result := 0;
end;
{$endif}
begin
// set logging abilities
SQLite3Log.Family.Level := LOG_VERBOSE;
@@ -190,10 +191,10 @@ begin
SQLite3Log.Family.NoFile := true; // do not create log files for benchmark
{$ifdef UNIX}
{$ifdef LINUXNOTBSD}
if SynSystemd.ProcessIsStartedBySystemd then begin
if ProcessIsStartedBySystemd then begin
SQLite3Log.Family.EchoToConsole := SQLite3Log.Family.Level;
SQLite3Log.Family.EchoToConsoleUseJournal := true;
if sd.listen_fds(0) = 1 then
if ExternalLibraries.sd_listen_fds(0) = 1 then
url := '' // force to use socket passed by systemd
else
url := '8888';

View File

@@ -43,6 +43,15 @@
<OtherUnitFiles Value="../..;../../.."/>
<UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
<CodeGeneration>
<SmartLinkUnit Value="True"/>
</CodeGeneration>
<Linking>
<Debugging>
<UseExternalDbgSyms Value="True"/>
</Debugging>
<LinkSmart Value="True"/>
</Linking>
</CompilerOptions>
<Debugging>
<Exceptions Count="1">

View File

@@ -7,7 +7,7 @@ unit SynJSONTreeView;
{
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2020 Arnaud Bouchez
Synopse framework. Copyright (C) 2022 Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
@@ -26,7 +26,7 @@ unit SynJSONTreeView;
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (C) 2020
Portions created by the Initial Developer are Copyright (C) 2022
the Initial Developer. All Rights Reserved.
Contributor(s):

View File

@@ -6,7 +6,7 @@ unit SynRestMidasVCL;
{
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2020 Arnaud Bouchez
Synopse framework. Copyright (C) 2022 Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
@@ -25,7 +25,7 @@ unit SynRestMidasVCL;
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (C) 2020
Portions created by the Initial Developer are Copyright (C) 2022
the Initial Developer. All Rights Reserved.
Contributor(s):

View File

@@ -6,7 +6,7 @@ unit SynRestVCL;
{
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2020 Arnaud Bouchez
Synopse framework. Copyright (C) 2022 Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
@@ -25,7 +25,7 @@ unit SynRestVCL;
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (C) 2020
Portions created by the Initial Developer are Copyright (C) 2022
the Initial Developer. All Rights Reserved.
Contributor(s):

View File

@@ -7,7 +7,7 @@ unit mORMotRESTFPCInterfaces;
This unit has been generated by a mORMot 1.18.2797 server.
Any manual modification of this file may be lost after regeneration.
Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez
Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez
Synopse Informatique - https://synopse.info
This unit is released under a MPL/GPL/LGPL tri-license,

View File

@@ -1,10 +1,57 @@
{$IF (CompilerVersion <= 25)}
type
//other name of constants in XE4
TStyledSettingHelper = record helper for TStyledSetting
const Family = TStyledSetting.ssFamily;
const Size = TStyledSetting.ssSize;
const Style = TStyledSetting.ssStyle;
const FontColor = TStyledSetting.ssFontColor;
const Other = TStyledSetting.ssOther;
end;
TTextAlignHelper = record helper for TTextAlign
const Center = TTextAlign.taCenter;
const Leading = TTextAlign.taLeading;
const Trailing = TTextAlign.taTrailing;
end;
TFmxFormBorderStyleHelper = record helper for TFmxFormBorderStyle
const None = TFmxFormBorderStyle.bsNone;
const Single = TFmxFormBorderStyle.bsSingle;
const Sizeable = TFmxFormBorderStyle.bsSizeable;
const ToolWindow = TFmxFormBorderStyle.bsToolWindow;
const SizeToolWin = TFmxFormBorderStyle.bsSizeToolWin;
end;
TFormPositionHelper = record helper for TFormPosition
//(poDesigned, poDefault, poDefaultPosOnly, poDefaultSizeOnly,
//poScreenCenter, poDesktopCenter, poMainFormCenter, poOwnerFormCenter);
const OwnerFormCenter = TFormPosition.poOwnerFormCenter;
const ScreenCenter = TFormPosition.poScreenCenter;
end;
TBrushKindHelper = record helper for TBrushKind
//(bkNone, bkSolid, bkGradient, bkBitmap, bkResource);
const None = TBrushKind.bkNone;
end;
TAlignLayoutHelper = record helper for TAlignLayout
//(alNone, alTop, alLeft, alRight, alBottom, alMostTop, alMostBottom, alMostLeft, alMostRight, alClient,
//alContents, alCenter, alVertCenter, alHorzCenter, alHorizontal, alVertical, alScale, alFit, alFitLeft, alFitRight);
const Top = TAlignLayout.alTop;
end;
{$IFEND}
var
_ScreenDPI_X : Single = 0;
function ScalingByScreenDPI_N( F:TForm = NIL ):Single;
var
p : TPointF;
{$IF (CompilerVersion >= 28)}
M : TDeviceDisplayMetrics;
{$IFEND}
i : integer;
h : THandle;
begin
@@ -29,6 +76,7 @@ begin
end;
{$ENDIF}
{$IF (CompilerVersion >= 28)} //TDeviceDisplayMetrics is available since XE8
if TPlatformServices.Current.SupportsPlatformService( IFMXDeviceMetricsService ) then
begin
M := (TPlatformServices.Current.GetPlatformService(
@@ -38,6 +86,7 @@ begin
{$IFDEF MSWINDOWS}96{$ENDIF}
;
end;
{$IFEND}
end;
function ScalingByScreenDPI( F:TForm = NIL ):TPointF;

View File

@@ -6,7 +6,7 @@ unit SynTaskDialog;
{
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2020 Arnaud Bouchez
Synopse framework. Copyright (C) 2022 Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
@@ -25,7 +25,7 @@ unit SynTaskDialog;
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (C) 2020
Portions created by the Initial Developer are Copyright (C) 2022
the Initial Developer. All Rights Reserved.
Contributor(s):
@@ -139,8 +139,12 @@ uses
{$endif}
{$IFDEF FMX}
System.UITypes, System.Types, System.UIConsts,
FMX.Menus, FMX.Types, FMX.Layouts, FMX.ComboEdit,
FMX.Graphics, FMX.Forms, FMX.Controls, FMX.StdCtrls, FMX.ExtCtrls,
FMX.Menus, FMX.Types, FMX.Layouts,
{$IF (CompilerVersion >= 26.0)}// Delphi XE5 UP
FMX.ComboEdit,
FMX.Graphics,
{$IFEND}
FMX.Forms, FMX.Controls, FMX.StdCtrls, FMX.ExtCtrls,
FMX.ListBox, FMX.Edit, FMX.Objects, FMX.Platform,
{$IFDEF MSWINDOWS}
FMX.Platform.Win