-
-
Notifications
You must be signed in to change notification settings - Fork 324
/
dddInfraSettings.pas
1207 lines (1105 loc) · 52.2 KB
/
dddInfraSettings.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/// shared DDD Infrastructure: Application/Daemon settings classes
// - this unit is a part of the freeware Synopse mORMot framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit dddInfraSettings;
{
This file is part of Synopse mORMot framework.
Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (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.mozilla.org/MPL
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is Synopse mORMot framework.
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (C) 2020
the Initial Developer. All Rights Reserved.
Contributor(s):
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****
Version 1.18
- first public release, corresponding to Synopse mORMot Framework 1.18
TODO:
- store settings in database, or in a centralized service
}
{$I Synopse.inc} // define HASINLINE DDDNOSYNDB DDDNOMONGODB WITHLOG
{.$define DDDNOSYNDB}
// if defined, SynDB / external SQL DB won't be linked to the executable
{.$define DDDNOMONGODB}
// if defined, the Mongo DB client won't be linked to the executable
interface
uses
{$ifdef MSWINDOWS}
Windows, // for DeleteFile() inlining under Delphi 2006/2007
{$endif}
SysUtils,
Classes,
SynCommons,
SynTable,
SynLog,
SynCrypto,
mORMot,
mORMotDDD,
SynCrtSock,
SynSQLite3,
mORMotSQLite3, // for internal SQlite3 database
{$ifndef DDDNOSYNDB}
SynDB,
mORMotDB, // for TDDDRestSettings on external SQL database
{$endif}
{$ifndef DDDNOMONGODB}
SynMongoDB,
mORMotMongoDB, // for TDDDRestSettings on external NoSQL database
{$endif}
mORMotWrappers; // for TDDDRestSettings to publish wrapper methods
{ ----- Manage Service/Daemon settings }
type
/// settings used to define how logging take place
// - will map the most used TSynLogFamily parameters
TDDDLogSettings = class(TSynPersistent)
protected
fLevels: TSynLogInfos;
fConsoleLevels: TSynLogInfos;
fAutoFlush: integer;
fStackTraceViaAPI: boolean;
fLowLevelWebSocketsFrames: boolean;
fDestinationPath: TFileName;
fCustomFileName: TFileName;
fRotateFileCount: cardinal;
fRotateFileSize: cardinal;
fRotateFileAtHour: integer;
fSyslogLevels: TSynLogInfos;
fSyslogFacility: TSyslogFacility;
fSyslogServer: RawUTF8;
public
/// initialize the settings to their (TSynLogFamily) default values
constructor Create; override;
published
/// the log levels to be used for the log file
// - i.e. a combination of none or several logging event
// - if "*" is serialized, unneeded sllNone won't be part of the set
property Levels: TSynLogInfos read fLevels write fLevels;
/// the optional log levels to be used for the console
// - by default, only errors would be logged to the console
// - you can specify here another set of levels, e.g. '*' for a verbose
// console output - note that console is very slow to write, so usually
// you should better not set a verbose definition here, unless you are
// in debugging mode
property ConsoleLevels: TSynLogInfos read fConsoleLevels write fConsoleLevels;
/// if low-level WebSockets frames should be logged
// - disabled by default, to minimize logged content
// - may be enabled to monitor most (asynchronous) activity, especially
// in background threads
property LowLevelWebSocketsFrames: boolean read fLowLevelWebSocketsFrames write fLowLevelWebSocketsFrames;
/// the time (in seconds) after which the log content must be written on
// disk, whatever the current content size is
// - by default, the log file will be written for every 4 KB of log (see
// TSynLogFamily.BufferSize property) - this will ensure that the main
// application won't be slow down by logging
// - in order not to loose any log, a background thread can be created
// and will be responsible of flushing all pending log content every
// period of time (e.g. every 10 seconds)
property AutoFlushTimeOut: integer read fAutoFlush write fAutoFlush;
/// by default (false), logging will use manual stack trace browsing
// - if you experiment unexpected EAccessViolation, try to set this setting
// to TRUE so that the RtlCaptureStackBackTrace() API would be used instead
property StackTraceViaAPI: boolean read FStackTraceViaAPI write FStackTraceViaAPI;
/// allows to customize where the log files will be stored
property DestinationPath: TFileName read FDestinationPath write FDestinationPath;
/// allows to customize the log file name
property CustomFileName: TFileName read fCustomFileName write fCustomFileName;
/// auto-rotation of logging files
// - set to 0 by default, meaning no rotation
property RotateFileCount: cardinal read fRotateFileCount write fRotateFileCount;
/// maximum size of auto-rotated logging files, in kilo-bytes (per 1024 bytes)
property RotateFileSizeKB: cardinal read fRotateFileSize write fRotateFileSize;
/// fixed hour of the day where logging files rotation should be performed
property RotateFileDailyAtHour: integer read fRotateFileAtHour write fRotateFileAtHour;
/// the optional log levels to be used for remote UDP syslog server sending
// - works in conjunction with SyslogServer property
// - default will transmit all warnings, errors and exceptions
property SyslogLevels: TSynLogInfos read fSyslogLevels write fSyslogLevels;
/// the optional log levels to be used for remote UDP syslog server sending
// - works in conjunction with SyslogServer/SyslogLevels properties
// - default is sfLocal0
property SyslogFacility: TSyslogFacility read fSyslogFacility write fSyslogFacility;
/// the optional remote UDP syslog server
// - expecting https://tools.ietf.org/html/rfc5424 messages over UDP
// - e.g. '1.2.3.4' to connect to UDP server 1.2.3.4 using default port 514 -
// but you can specify an alternative port as '1.2.3.4:2514'
// - works in conjunction with SyslogLevels/SyslogFacility properties
// - default is '' to disable syslog remote logging
property SyslogServer: RawUTF8 read fSyslogServer write fSyslogServer;
end;
TDDDAppSettingsAbstract = class;
/// abstract parent class for storing application settings
TDDDAppSettingsStorageAbstract = class(TSynAutoCreateFields)
protected
fInitialJsonContent: RawUTF8;
fOwner: TDDDAppSettingsAbstract;
/// called by TDDDAppSettingsAbstract.Create
function SetOwner(aOwner: TDDDAppSettingsAbstract): boolean;
/// inherited classes would override this to persist fInitialJsonContent
procedure InternalStore; virtual; abstract;
public
/// initialize the storage instance
constructor Create(const aInitialJSON: RawUTF8); reintroduce; virtual;
/// TDDDAppSettingsAbstract would use this to actually persist the data
procedure Store(const aJSON: RawUTF8); virtual;
/// the JSON content, as specified when creating the instance
// - will allow SettingsDidChange to check if has changed
// - here the JSON content is stored with default ObjectToJSON() options,
// so will be the normalized representation of the content, which may not
// match the JSON supplied to SetInitialJsonContent() protected method
property InitialJsonContent: RawUTF8 read fInitialJsonContent;
/// the associated settings values
property Owner: TDDDAppSettingsAbstract read fOwner;
end;
/// abstract class for storing application settings
// - this class implements IAutoCreateFieldsResolve so is able to inject
// its own values to any TInjectableAutoCreateFields instance
// - you have to manage instance lifetime of these inherited classes with a
// local IAutoCreateFieldsResolve variable, just like any TInterfaceObject
TDDDAppSettingsAbstract = class(TInterfacedObjectAutoCreateFields,
IAutoCreateFieldsResolve, IDDDSettingsStorable)
protected
fAllProps: PPropInfoDynArray;
fDescription: string;
fLog: TDDDLogSettings;
fSyslog: TCrtSocket;
fSyslogProcID: RawUTF8;
fStorage: TDDDAppSettingsStorageAbstract;
procedure SetProperties(Instance: TObject); virtual;
function SyslogEvent(Sender: TTextWriter; Level: TSynLogInfo;
const Text: RawUTF8): boolean;
public
/// initialize the settings, with a corresponding storage process
constructor Create(aStorage: TDDDAppSettingsStorageAbstract); reintroduce;
/// persist if needed, and finalize the settings
destructor Destroy; override;
/// to be called when the application starts, to initialize settings
// - you can specify a default Description value
// - it will set the global SQLite3Log.Family according to Log values
procedure Initialize(const aDescription: string); virtual;
/// persist the settings if needed
// - just a wrapper around Storage.Store(AsJson)
// - implements IDDDSettingsStorable for "#settings save" admin command
procedure StoreIfUpdated; virtual;
/// serialize the settings as JSON
// - any enumerated or set published property will be commented with their
// textual values, and 'stored false' properties would be included
// - returns the new JSON content corresponding to the updated settings
function AsJson: RawUTF8; virtual;
/// low-level method returning all TSynPersistentPassword full paths
// of all previously created TDDDAppSettingsStorageFile .settings
// - as settingsfile=class1@full.path.to.pass1,class2@full.path.to.pass2,...
// - you may use this method to create a 'passwords' resource for
// /HardenPasswords command line switch as implemented in dddInfraSettings.pas:
// ! passwords := SynLZCompress(TDDDAppSettingsAbstract.PasswordFields);
// ! FileFromString(passwords, 'passwords.data');
// then create e.g. a passwords.rc file as such:
// $ passwords 10 "passwords.data"
// compile this resource:
// $ brcc32 passwords.rc
// and link the resulting .res file to your daemon executable:
// ! {$R passwords.res}
// then /HardenPasswords and /PlainPasswords command line switchs will
// cypher/uncypher all TSynPersistentPassword protected fields using safe
// per-user CryptDataForCurrentUser() encryption
class function PasswordFields: RawUTF8;
/// access to the associated settings storage
property Storage: TDDDAppSettingsStorageAbstract read fStorage;
/// transmitted as PROCID as part of any Log.SyslogServer message
property SyslogProcID: RawUTF8 read fSyslogProcID write fSyslogProcID;
published
/// some text which will be used to describe this application
property Description: string read FDescription write FDescription;
/// defines how logging will be done for this application
property Log: TDDDLogSettings read fLog;
end;
/// class type used for storing application settings
TDDDAppSettingsAbstractClass = class of TDDDAppSettingsAbstract;
/// class used for storing application settings as a JSON file
TDDDAppSettingsStorageFile = class(TDDDAppSettingsStorageAbstract)
protected
fSettingsJsonFileName: TFileName;
procedure InternalStore; override;
public
/// initialize and read the settings from the supplied JSON file name
// - if no file name is specified, will use the executable name with
// '.settings' as extension
constructor Create(const aSettingsJsonFileName: TFileName=''); reintroduce; virtual;
/// compute a file name relative to the .settings file path
function FileNameRelativeToSettingsFile(const aFileName: TFileName): TFileName;
/// the .settings file name, including full path
property SettingsJsonFileName: TFileName
read fSettingsJsonFileName write fSettingsJsonFileName;
end;
/// some options to be used for TDDDRestSettings
// - as part of the .settings, they may be tuned for specific installations,
// whereas TDDDNewRestInstanceOptions are defined in code
TDDDRestSettingsOption =
(optEraseDBFileAtStartup,
optSQlite3FileSafeSlowMode,
optSQlite3FileSafeNonExclusive,
optNoSystemUse,
optSQlite3File4MBCacheSize,
optForceAjaxJson,
optSQLite3LogQueryPlan);
/// define options to be used for TDDDRestSettings
TDDDRestSettingsOptions = set of TDDDRestSettingsOption;
/// how TDDDRestSettings.NewRestInstance would create its instances
// - riOwnModel will set ModelInstance.Owner := RestInstance
// - riHandleAuthentication will set the corresponding parameter to true
// - riDefaultLocalSQlite3IfNone/riDefaultInMemorySQLite3IfNone will create
// a SQLite3 engine with a local file/memory storage, if
// TDDDRestSettings.ORM.Kind is not set
// - riDefaultFullMemoryIfNone will create a TSQLRestServerFullMemory non
// persistent storage, or riDefaultLocalBinaryFullMemoryIfNone with a
// binary local file, if TDDDRestSettings.ORM.Kind is not set
// - riCreateMissingTables will call RestInstance.CreateMissingTables
// - riRaiseExceptionIfNoRest will raise an EDDDInfraException if
// TDDDRestSettings.NewRestInstance would return nil
// - riWithInternalState will enable 'Server-InternalState:' header transmission
// i.e. disable rsoNoInternalState for TSQLRestServer.Options
TDDDNewRestInstanceOptions = set of (
riOwnModel, riCreateVoidModelIfNone,
riHandleAuthentication,
riDefaultLocalSQlite3IfNone, riDefaultInMemorySQLite3IfNone,
riDefaultFullMemoryIfNone, riDefaultLocalBinaryFullMemoryIfNone,
riCreateMissingTables,
riRaiseExceptionIfNoRest,
riWithInternalState);
/// storage class for initializing an ORM REST class
// - this class will contain some generic properties to initialize a TSQLRest
// pointing to a local or remote SQL/NoSQL database, with optional wrappers
TDDDRestSettings = class(TSynAutoCreateFields)
protected
fORM: TSynConnectionDefinition;
fDefaultDataFolder: TFileName;
fDefaultDataFileName: RawUTF8;
fRoot: RawUTF8;
fWrapperTemplateFolder: TFileName;
fWrapperSourceFolders: TFileName;
fOptions: TDDDRestSettingsOptions;
fWrapperTemplateFolderFixed: TFileName;
fWrapperSourceFolderFixed: TFileName;
public
/// is able to instantiate a REST instance according to the stored definition
// - Definition.Kind will identify the TSQLRestServer or TSQLRestClient class
// to be instantiated, or if equals 'MongoDB'/'MongoDBS' use a full MongoDB
// engine, or an external SQL database if it matches a TSQLDBConnectionProperties
// classname
// - if aDefaultLocalSQlite3 is TRUE, then if Definition.Kind is '',
// a local SQlite3 file database will be initiated
// - if aMongoDBIdentifier is not 0, then it will be supplied to every
// TSQLRestStorageMongoDB.SetEngineAddComputeIdentifier() created
// - will return nil if the supplied Definition is not correct
// - note that the supplied Model.Root is expected to be the default root
// URI, which will be overriden with this TDDDRestSettings.Root property
// - will also publish /wrapper HTML page if WrapperTemplateFolder is set
function NewRestInstance(aRootSettings: TDDDAppSettingsAbstract;
aModel: TSQLModel; aOptions: TDDDNewRestInstanceOptions
{$ifndef DDDNOSYNDB}; aExternalDBOptions:
TVirtualTableExternalRegisterOptions=[regDoNotRegisterUserGroupTables]{$endif}
{$ifndef DDDNOMONGODB}; aMongoDBIdentifier: word=0; aMongoDBOptions:
TStaticMongoDBRegisterOptions=[mrDoNotRegisterUserGroupTables]{$endif}): TSQLRest; overload; virtual;
/// is able to instantiate a REST instance according to the stored definition
// - just an overloaded version which will create an owned TSQLModel with
// the supplied TSQLRecord classes
function NewRestInstance(aRootSettings: TDDDAppSettingsAbstract;
const aTables: array of TSQLRecordClass; aOptions: TDDDNewRestInstanceOptions
{$ifndef DDDNOSYNDB}; aExternalDBOptions:
TVirtualTableExternalRegisterOptions=[regDoNotRegisterUserGroupTables]{$endif}
{$ifndef DDDNOMONGODB}; aMongoDBIdentifier: word=0; aMongoDBOptions:
TStaticMongoDBRegisterOptions=[mrDoNotRegisterUserGroupTables]{$endif}): TSQLRest; overload; virtual;
/// initialize a stand-alone TSQLRestServerDB instance
// - with its own database file located in DefaultDataFileName + aDBFileName
// - will own its own TSQLModel with aModelRoot/aModelTables
// - you can tune aCacheSize if the default 40MB value is not right
// - will eventually call CreateMissingTables
// - define custom TDDDRestSettingsOptions if needed
function NewRestServerDB(const aDBFileName: TFileName; const aModelRoot: RawUTF8;
const aModelTables: array of TSQLRecordClass; aOptions: TDDDRestSettingsOptions=[];
aCacheSize: cardinal=10000): TSQLRestServerDB;
/// if DB is a TSQLRestServerDB, will define the expection options
// - DB.FileName will be erased from disk if optEraseDBFileAtStartup is defined
// - force LockingMode=exclusive and Synchrounous=off unless
// optSQlite3FileSafeNonExclusive/optSQlite3FileSafeSlowMode options are set
class procedure RestServerDBSetOptions(DB: TSQLRestServer; Options: TDDDRestSettingsOptions);
/// returns the WrapperTemplateFolder property, all / chars replaced by \
// - so that you would be able to store the paths with /, avoiding JSON escape
function WrapperTemplateFolderFixed(ReturnLocalIfNoneSet: boolean=false): TFileName;
/// returns the WrapperSourceFolder property, all / chars replaced by \
// - so that you would be able to store the paths with /, avoiding JSON escape
function WrapperSourceFolderFixed: TFileName;
/// generate API documentation corresponding to REST SOA interfaces
procedure WrapperGenerate(Rest: TSQLRestServer; Port: integer;
const DestFile: TFileName; const Template: TFileName = 'API.adoc.mustache');
/// the default folder where database files are to be stored
// - will be used by NewRestInstance instead of the .exe folder, if set
property DefaultDataFolder: TFileName read fDefaultDataFolder write fDefaultDataFolder;
/// the default database file name
property DefaultDataFileName: RawUTF8 read fDefaultDataFileName write fDefaultDataFileName;
published
/// the URI Root to be used for the REST Model
property Root: RawUTF8 read fRoot write fRoot;
/// defines a mean of access to a TSQLRest instance
// - using Kind/ServerName/DatabaseName/User properties: Kind would define
// the TSQLRest class to be instantiated by function NewRestInstance()
property ORM: TSynConnectionDefinition read fORM;
/// if set to a valid folder, the generated TSQLRest will publish a
// '/Root/wrapper' HTML page so that client code could be generated
property WrapperTemplateFolder: TFileName
read fWrapperTemplateFolder write fWrapperTemplateFolder;
/// where the source code may be searched, for comment extraction of types
// - several folders may be defined, separated by ; (just like in Delphi IDE)
// - only used if WrapperTemplateFolder is defined
property WrapperSourceFolders: TFileName
read fWrapperSourceFolders write fWrapperSourceFolders;
/// how the REST instance is to be initialized
property Options: TDDDRestSettingsOptions read fOptions write fOptions;
end;
/// parent class for storing REST-based application settings
// - this class could be used for an application with a single REST server
// running on a given HTTP port
TDDDAppSettingsRest = class(TDDDAppSettingsAbstract)
protected
fRest: TDDDRestSettings;
fServerPort: RawUTF8;
public
/// to be called when the application starts, to initialize settings
// - will call inherited TDDDAppSettingsFile.Initialize, and
// set ServerPort to a default 888/8888 value under Windows/Linux
procedure Initialize(const aDescription: string); override;
published
/// allow to instantiate a REST instance from its JSON definition
property Rest: TDDDRestSettings read fRest;
/// the IP port to be used for the HTTP server associated with the application
property ServerPort: RawUTF8 read fServerPort write fServerPort;
end;
/// define how an administrated service/daemon is remotely accessed via REST
// - the IAdministratedDaemon service will be published to administrate
// this service/daemon instance
// - those values should match the ones used on administrative tool side
TDDDAdministratedDaemonRemoteAdminSettings = class(TSynAutoCreateFields)
protected
FAuthRootURI: RawUTF8;
FAuthHashedPassword: RawUTF8;
FAuthUserName: RawUTF8;
FAuthNamedPipeName: TFileName;
FAuthHttp: TSQLHttpServerDefinition;
public
published
/// the root URI used for the REST data model
// - default URI is 'admin'
property AuthRootURI: RawUTF8 read FAuthRootURI write FAuthRootURI;
/// if set, expect authentication with this single user name
// - that is, the TSQLRestServer will register a single TSQLAuthUser
// instance with the supplied AuthUserName/AuthHashedPassword credentials
property AuthUserName: RawUTF8 read FAuthUserName write FAuthUserName;
/// the SHA-256 hashed password to authenticate AuthUserName
// - follows the TSQLAuthUser.ComputeHashedPassword() encryption
// - marked as 'stored false' so that it won't appear e.g. in the logs
property AuthHashedPassword: RawUTF8 read FAuthHashedPassword write FAuthHashedPassword
stored false;
/// if defined, the following pipe name would be used for REST publishing
// - by definition, will work only on Windows
property AuthNamedPipeName: TFileName read FAuthNamedPipeName write FAuthNamedPipeName;
/// if defined, these parameters would be used for REST publishing over HTTP
property AuthHttp: TSQLHttpServerDefinition read FAuthHttp;
end;
/// parent class for storing a service/daemon settings
// - under Windows, some Service* properties will handle installation as a
// regular Windows Service, thanks to TDDDDaemon
TDDDAdministratedDaemonSettings = class(TDDDAppSettingsAbstract)
protected
FRemoteAdmin: TDDDAdministratedDaemonRemoteAdminSettings;
FServiceDisplayName: string;
FServiceName: string;
FServiceDependencies: TStringDynArray;
FServiceAutoStart: boolean;
FAppUserModelID: string;
public
/// to be called when the application starts, to initialize settings
// - you can specify default Description and Service identifiers
// - the service-related parameters are Windows specific, and will be
// ignored on other platforms
procedure Initialize(const aDescription,
aServiceName,aServiceDisplayName,aAppUserModelID: string;
const aServiceDependencies: TStringDynArray = nil); reintroduce; virtual;
/// returns the folder containing .settings files - .exe folder by default
function SettingsFolder: TFileName; virtual;
/// under Windows, will define optional Service internal Dependencies
// - not published by default: could be defined if needed, or e.g. set in
// overriden constructor
property ServiceDependencies: TStringDynArray read FServiceDependencies write FServiceDependencies;
published
/// define how this administrated service/daemon is accessed via REST
property RemoteAdmin: TDDDAdministratedDaemonRemoteAdminSettings read FRemoteAdmin;
/// under Windows, will define the Service internal name
property ServiceName: string read FServiceName write FServiceName;
/// under Windows, will define the Service displayed name
property ServiceDisplayName: string read FServiceDisplayName write FServiceDisplayName;
/// under Windows, will define if the Service should auto-start at boot
// - FALSE means that it should be started on demand
property ServiceAutoStart: boolean read FServiceAutoStart write FServiceAutoStart;
/// under Windows 7 and later, will set an unique application-defined
// Application User Model ID (AppUserModelID) that identifies the current
// process to the taskbar
// - this identifier allows an application to group its associated processes
// and windows under a single taskbar button
// - should follow SetAppUserModelID() expectations, i.e. 'Company.Product'
property AppUserModelID: string read FAppUserModelID write FAppUserModelID;
end;
/// a Factory event allowing to customize/mock a socket connection
// - the supplied aOwner should be a TDDDSocketThread instance
// - returns a IDDDSocket interface instance (e.g. a TDDDSynCrtSocket)
TOnIDDDSocketThreadCreate = procedure(aOwner: TObject; out Obj) of object;
/// the settings of a TDDDThreadSocketProcess thread
// - defines how to connect (and reconnect) to the associated TCP server
TDDDSocketThreadSettings = class(TSynAutoCreateFields)
protected
fHost: RawUTF8;
fPort: integer;
fSocketLoopPeriod: integer;
fSocketTimeout: integer;
fSocketBufferBytes: integer;
fSocketMaxBufferBytes: integer;
fConnectionAttemptsInterval: Integer;
fAutoReconnectAfterSocketError: boolean;
fMonitoringInterval: integer;
fOnIDDDSocketThreadCreate: TOnIDDDSocketThreadCreate;
public
/// used to set the default values
constructor Create; override;
/// set Host and Port values from a 'ip:port' or 'ip' text
function SetHostPort(const IpPort: RawByteString; defaultPort: integer): boolean;
/// retrieve Host and Port values as a single 'ip:port' text
function GetHostPort: RawUTF8;
/// you could set here a factory method to mock the socket connection
// - this property is public, but not published, since it should not be
// serialized on the settings file, but overloaded at runtime
property OnIDDDSocketThreadCreate: TOnIDDDSocketThreadCreate
read fOnIDDDSocketThreadCreate write fOnIDDDSocketThreadCreate;
published
/// the associated TCP server host
property Host: RawUTF8 read FHost write FHost;
/// the associated TCP server port
property Port: integer read FPort write FPort;
/// how many millisecond the main socket reading loop should wait
// for pending data, before calling TDDDSocketThread.InternalExecuteIdle
// - default is 100 ms
property SocketLoopPeriod: integer read fSocketLoopPeriod write fSocketLoopPeriod;
/// the time out period, in milliseconds, for socket access
// - default is 2000 ms, i.e. 2 seconds
property SocketTimeout: integer read FSocketTimeout write FSocketTimeout;
/// the internal size of the input socket buffer
// - default is 32768, i.e. 32 KB
property SocketBufferBytes: integer read FSocketBufferBytes write FSocketBufferBytes;
/// the maximum size of the thread input buffer
// - i.e. how many bytes are stored in fSocketInputBuffer memory, before
// nothing is retrieved from the socket buffer
// - set to avoid any "out of memory" of the currrent process, if the
// incoming data is not processed fast enough
// - default is 16777216, i.e. 16 MB
property SocketMaxBufferBytes: integer read FSocketMaxBufferBytes write FSocketMaxBufferBytes;
/// the time, in seconds, between any reconnection attempt
// - default value is 5 - i.e. five seconds
// - if you set -1 as value, thread would end without any retrial
property ConnectionAttemptsInterval: Integer
read fConnectionAttemptsInterval write fConnectionAttemptsInterval;
/// if TRUE, any communication error would try to reconnect the socket
property AutoReconnectAfterSocketError: boolean
read FAutoReconnectAfterSocketError write FAutoReconnectAfterSocketError;
/// the period, in milliseconds, on which Monitoring information is logged
// - default value is 120000, i.e. 2 minutes
property MonitoringLogInterval: integer read FMonitoringInterval write FMonitoringInterval;
end;
/// storage class for a ServicesLog settings
TDDDServicesLogRestSettings = class(TDDDRestSettings)
protected
fShardDBCount: Integer;
public
/// compute a stand-alone REST instance for interface-based services logging
// - all services of aMainRestWithServices would log their calling information
// into a dedicated table, but the methods defined in aExcludedMethodNamesCSV
// (which should be specified, even as '', to avoid FPC compilation error)
// - by default, will create a local SQLite3 file for storage, optionally
// via TSQLRestStorageShardDB if ShardDBCount is set
// - the first supplied item of aLogClass array would be used for the
// service logging; any additional item would be part of the model of the
// returned REST instance, but may be used later on (e.g. to handle
// DB-based asynchronous remote notifications as processed by
// TServiceFactoryClient.SendNotificationsVia method)
// - if aLogClass=[], plain TSQLRecordServiceLog would be used as default
// - aShardRange is used for TSQLRestStorageShardDB if ShardDBCount>0
function NewRestInstance(aRootSettings: TDDDAppSettingsAbstract;
aMainRestWithServices: TSQLRestServer; const aLogClass: array of TSQLRecordServiceLogClass;
const aExcludedMethodNamesCSV: RawUTF8; aShardRange: TID=50000): TSQLRest; reintroduce;
published
/// if set, will define MaxShardCount for TSQLRestStorageShardDB persistence
property ShardDBCount: Integer read fShardDBCount write fShardDBCount;
end;
/// parent class for storing a HTTP published service/daemon settings
TDDDAdministratedDaemonHttpSettings = class(TDDDAdministratedDaemonSettings)
protected
fRest: TDDDRestSettings;
fHttp: TSQLHttpServerDefinition;
fServicesLog: TDDDServicesLogRestSettings;
published
/// how the main REST server is implemented
// - most probably using a TSQLRestServerDB, i.e. local SQLite3 storage
property Rest: TDDDRestSettings read fRest;
/// how the HTTP server should be defined
property Http: TSQLHttpServerDefinition read fHttp;
/// how the SOA calls would be logged into their own SQlite3 database
property ServicesLog: TDDDServicesLogRestSettings read fServicesLog;
end;
/// stand-alone property to publish a secondary TSQLRestServer over HTTP
TDDDRestHttpSettings = class(TSynAutoCreateFields)
protected
fRest: TDDDRestSettings;
fHttp: TSQLHttpServerDefinition;
published
/// how the REST server is implemented
// - most probably using a TSQLRestServerDB, i.e. local SQLite3 storage
property Rest: TDDDRestSettings read fRest;
/// how the HTTP server should be defined
property Http: TSQLHttpServerDefinition read fHttp;
end;
/// stand-alone property to publish a secondary logged service over HTTP
TDDDRestHttpLogSettings = class(TDDDRestHttpSettings)
protected
fServicesLog: TDDDServicesLogRestSettings;
published
/// how the SOA calls would be logged into their own SQlite3 database
property ServicesLog: TDDDServicesLogRestSettings read fServicesLog;
end;
/// storage class for a remote MongoDB server direct access settings
TDDDMongoDBRestSettings = class(TDDDRestSettings)
public
/// set the default values for direct MongoDB server connection
// - if MongoServerAddress is e.g. '?:27017', entry with default value would
// be saved in the settings, but NewRestInstance() would ignore it: once the
// remote MongoDB server IP is known, you may just replace '?' to use it
// - if MongoUser and MongoPassword are set, would call TMongoClient.OpenAuth()
procedure SetDefaults(const Root, MongoServerAddress, MongoDatabase,
MongoUser, MongoPassword: RawUTF8; TLS: boolean=false);
end;
TDDDEmailerSettings = class(TSynPersistent)
protected
fSMTP: RawUTF8;
fRecipients: RawUTF8;
public
constructor Create; override;
published
property SMTP: RawUTF8 read fSMTP write fSMTP;
property Recipients: RawUTF8 read fRecipients write fRecipients;
end;
implementation
{ TDDDAppSettingsAbstract }
procedure TDDDAppSettingsAbstract.Initialize(const aDescription: string);
var
uri: TURI;
begin
{$ifdef WITHLOG}
with SQLite3Log.Family do begin
Level := Log.Levels-[sllNone]; // '*' would include sllNone
if Log.ConsoleLevels<>[] then
EchoToConsole := Log.ConsoleLevels-[sllNone];
PerThreadLog := ptIdentifiedInOnFile;
if Log.DestinationPath<>'' then
DestinationPath := Log.DestinationPath;
if Log.CustomFileName<>'' then
CustomFileName := Log.CustomFileName;
RotateFileCount := Log.RotateFileCount;
RotateFileSizeKB := Log.RotateFileSizeKB;
RotateFileDailyAtHour := Log.RotateFileDailyAtHour;
if Log.RotateFileCount<=0 then
HighResolutionTimestamp := true;
FileExistsAction := acAppend; // default rotation mode
if Log.StackTraceViaAPI then
StackTraceUse := stOnlyAPI;
// AutoFlushTimeOut not set now, since won't work with /form
if (Log.SyslogServer<>'') and (Log.SyslogServer[1]<>'?') and
not Assigned(EchoCustom) and (fSyslog=nil) and (Log.SyslogLevels<>[]) and
uri.From(Log.SyslogServer,'514') then
try
fSyslog := TCrtSocket.Open(uri.Server,uri.Port,cslUDP,2000);
EchoCustom := SyslogEvent;
except
fSyslog := nil;
end;
end;
{$endif}
if fDescription='' then
fDescription := aDescription;
end;
function TDDDAppSettingsAbstract.SyslogEvent(Sender: TTextWriter; Level: TSynLogInfo;
const Text: RawUTF8): boolean;
var
buf: array[0..511] of AnsiChar; // 512 bytes for fast unfragmented UDP packet
len: PtrInt;
begin
result := false;
if (fSyslog=nil) or not (Level in Log.SyslogLevels) then
exit;
len := SyslogMessage(Log.SyslogFacility,LOG_TO_SYSLOG[Level],Text,
fSyslogProcID,ToText(Level),@buf,sizeof(buf),true);
if len<>0 then
if fSyslog.TrySndLow(@buf,len) then // works even if no server is available
result := true else
raise ESynException.CreateUTF8('%.SyslogEvent failed for %:% as error %',
[self,fSyslog.Server,fSyslog.Port,fSyslog.LastLowSocketError]);
end;
procedure TDDDAppSettingsAbstract.SetProperties(Instance: TObject);
begin
CopyObject(self,Instance);
end;
destructor TDDDAppSettingsAbstract.Destroy;
begin
StoreIfUpdated;
inherited Destroy;
fStorage.Free;
if fSyslog<>nil then begin
{$ifdef WITHLOG}
SQLite3Log.Family.EchoCustom := nil;
{$endif}
FreeAndNil(fSyslog);
end;
end;
function TDDDAppSettingsAbstract.AsJson: RawUTF8;
begin
result := ObjectToJSON(Self,[woHumanReadable,woStoreStoredFalse,
woHumanReadableFullSetsAsStar,woHumanReadableEnumSetAsComment]);
end;
procedure TDDDAppSettingsAbstract.StoreIfUpdated;
begin
if fStorage<>nil then
fStorage.Store(AsJson);
end;
var
TDDDAppSettingsAbstractFiles: TRawUTF8List;
constructor TDDDAppSettingsAbstract.Create(aStorage: TDDDAppSettingsStorageAbstract);
begin
inherited Create;
if aStorage=nil then
aStorage := TDDDAppSettingsStorageFile.Create;
fStorage := aStorage;
fStorage.SetOwner(self);
if aStorage.InheritsFrom(TDDDAppSettingsStorageFile) then begin
if TDDDAppSettingsAbstractFiles=nil then
GarbageCollectorFreeAndNil(TDDDAppSettingsAbstractFiles,TRawUTF8List.Create);
TDDDAppSettingsAbstractFiles.AddObject(StringToUTF8(ExtractFileName(
TDDDAppSettingsStorageFile(aStorage).fSettingsJsonFileName)),pointer(ClassType));
end;
end;
class function TDDDAppSettingsAbstract.PasswordFields: RawUTF8;
procedure InternalAdd(const path: RawUTF8; C: TClass; var res: TRawUTF8DynArray);
var PI,PP: PPropInfo;
CT: TClass;
p: RawUTF8;
i: integer;
offset: pointer;
begin
offset := TSynPersistentWithPassword(nil).GetPasswordFieldAddress;
while C<>nil do begin
for i := 1 to InternalClassPropInfo(C,PI) do begin
if PI^.PropType^.Kind=tkClass then begin
FormatUTF8('%%.',[path,PI^.Name],p);
CT := PI^.PropType^.ClassType^.ClassType;
if CT.InheritsFrom(TSynPersistentWithPassword) then begin
PP := ClassFieldPropWithParentsFromClassOffset(CT,offset);
if PP<>nil then
AddRawUTF8(res,FormatUTF8('%@%%', [ClassNameShort(CT)^, p, PP^.Name]));
end;
InternalAdd(p,CT,res); // recursive search of all password fields
end;
PI := PI^.Next;
end;
C := GetClassParent(C);
end;
end;
var i: integer;
fn: RawUTF8;
res: TRawUTF8DynArray;
cl: TDDDAppSettingsAbstractClass;
begin
result := '';
if TDDDAppSettingsAbstractFiles<>nil then
for i := 0 to TDDDAppSettingsAbstractFiles.Count-1 do begin
fn := Split(TDDDAppSettingsAbstractFiles.Strings[i],'.');
cl := pointer(TDDDAppSettingsAbstractFiles.Objects[i]);
res := nil;
InternalAdd('',cl,res);
if res<>nil then
result := FormatUTF8('%%=%'#13#10,[result,fn,RawUTF8ArrayToCSV(res)]);
end;
end;
{ TDDDLogSettings }
constructor TDDDLogSettings.Create;
begin
inherited Create;
fLevels := [low(TSynLogInfo)..high(TSynLogInfo)]; // "Levels":"*" by default
fRotateFileAtHour := -1;
fRotateFileCount := 20;
fRotateFileSize := 128*1024; // 128 MB per rotation log by default
fAutoFlush := 5;
fSyslogLevels := [sllWarning,sllLastError,sllError,
sllException,sllExceptionOS,sllNewRun,sllDDDError];
fSyslogFacility := sfLocal0;
end;
{ TDDDRestSettings }
function TDDDRestSettings.NewRestInstance(aRootSettings: TDDDAppSettingsAbstract;
const aTables: array of TSQLRecordClass; aOptions: TDDDNewRestInstanceOptions
{$ifndef DDDNOSYNDB}; aExternalDBOptions: TVirtualTableExternalRegisterOptions {$endif}
{$ifndef DDDNOMONGODB}; aMongoDBIdentifier: word;
aMongoDBOptions: TStaticMongoDBRegisterOptions{$endif}): TSQLRest;
begin
include(aOptions,riOwnModel);
result := NewRestInstance(aRootSettings,TSQLModel.Create(aTables,fRoot),aOptions
{$ifndef DDDNOSYNDB},aExternalDBOptions{$endif}
{$ifndef DDDNOMONGODB},aMongoDBIdentifier,aMongoDBOptions{$endif});
end;
function TDDDRestSettings.NewRestInstance(aRootSettings: TDDDAppSettingsAbstract;
aModel: TSQLModel; aOptions: TDDDNewRestInstanceOptions
{$ifndef DDDNOSYNDB}; aExternalDBOptions: TVirtualTableExternalRegisterOptions{$endif}
{$ifndef DDDNOMONGODB}; aMongoDBIdentifier: word;
aMongoDBOptions: TStaticMongoDBRegisterOptions{$endif}): TSQLRest;
procedure ComputeDefaultORMServerName(const Ext: RawUTF8);
var FN: RawUTF8;
begin
if fORM.ServerName='' then begin
if fDefaultDataFolder='' then
fDefaultDataFolder := ExeVersion.ProgramFilePath;
if fDefaultDataFileName='' then
FN := ExeVersion.ProgramName else
FN := fDefaultDataFileName;
fORM.ServerName := StringToUTF8(IncludeTrailingPathDelimiter(
fDefaultDataFolder))+FN+Ext;
end;
end;
begin
if aModel=nil then
if riCreateVoidModelIfNone in aOptions then begin
aModel := TSQLModel.Create([],fRoot);
include(aOptions,riOwnModel);
end else
raise EDDDInfraException.CreateUTF8('%.NewRestInstance(aModel=nil)',[self]);
if fRoot='' then // supplied TSQLModel.Root is the default root URI
fRoot := aModel.Root else
aModel.Root := fRoot;
if fORM.Kind='' then
if riDefaultLocalSQlite3IfNone in aOptions then begin
fORM.Kind := 'TSQLRestServerDB'; // SQlite3 engine by default
ComputeDefaultORMServerName('.db');
end else
if riDefaultInMemorySQLite3IfNone in aOptions then begin
fORM.Kind := 'TSQLRestServerDB';
fORM.ServerName := SQLITE_MEMORY_DATABASE_NAME;
end else
if riDefaultFullMemoryIfNone in aOptions then
fORM.Kind := 'TSQLRestServerFullMemory' else
if riDefaultLocalBinaryFullMemoryIfNone in aOptions then begin
fORM.Kind := 'TSQLRestServerFullMemory';
fORM.DatabaseName := 'binary'; // as TSQLRestServerFullMemory.DefinitionTo
ComputeDefaultORMServerName('.data');
end;
result := nil;
try
if fORM.Kind='' then
exit;
if (optEraseDBFileAtStartup in Options) and (fORM.ServerName<>'') then
if (fORM.Kind='TSQLRestServerDB') or
(fORM.Kind='TSQLRestServerFullMemory') then
DeleteFile(UTF8ToString(fORM.ServerName));
{$ifndef DDDNOMONGODB}
result := TSQLRestMongoDBCreate(aModel,ORM,
riHandleAuthentication in aOptions,aMongoDBOptions,aMongoDBIdentifier);
{$endif}
{$ifdef DDDNOSYNDB}
result := TSQLRest.CreateTryFrom(aModel,ORM,riHandleAuthentication in aOptions);
{$else}
if result=nil then // failed to use MongoDB -> try external or internal DB
result := TSQLRestExternalDBCreate(aModel,ORM,
riHandleAuthentication in aOptions,aExternalDBOptions);
{$endif}
if result=nil then
exit; // no match or wrong parameters
if result.InheritsFrom(TSQLRestServer) then
try // initialize server features
if (WrapperTemplateFolder<>'') and DirectoryExists(WrapperTemplateFolderFixed) then
AddToServerWrapperMethod(TSQLRestServer(result),[WrapperTemplateFolderFixed],
WrapperSourceFolderFixed);
RestServerDBSetOptions(TSQLRestServer(result), Options);
if not (riWithInternalState in aOptions) then
TSQLRestServer(result).Options := TSQLRestServer(result).Options+[rsoNoInternalState];
if riCreateMissingTables in aOptions then
TSQLRestServer(result).CreateMissingTables;
except
FreeAndNil(result);
end; // note: TSQLRestClient.SetUser() has been called in TSQLRest*DBCreate()
if not(optNoSystemUse in Options) then
// if not already set, update cpu/ram info every 10 sec + 10 min history
result.SystemUseTrack(10);
finally
if riOwnModel in aOptions then
if result=nil then // avoid memory leak
aModel.Free else
aModel.Owner := result;
if (result=nil) and (riRaiseExceptionIfNoRest in aOptions) then
raise EDDDInfraException.CreateUTF8('Impossible to initialize % on %/%',
[fORM.Kind,fORM.ServerName,fRoot]);
end;
end;
class procedure TDDDRestSettings.RestServerDBSetOptions(DB: TSQLRestServer;
Options: TDDDRestSettingsOptions);
begin
if DB = nil then
exit;
if DB.InheritsFrom(TSQLRestServerDB) then
with TSQLRestServerDB(DB).DB do begin // tune internal SQlite3 engine
if optEraseDBFileAtStartup in Options then
DeleteFile(FileName);
if optSQlite3FileSafeNonExclusive in Options then
LockingMode := lmNormal else
LockingMode := lmExclusive;
if optSQlite3FileSafeSlowMode in Options then
Synchronous := smNormal else
Synchronous := smOff;
if optSQlite3File4MBCacheSize in Options then
CacheSize := (4 shl 20) div PageSize;
if optSQLite3LogQueryPlan in Options then
TSQLRestServerDB(DB).StatementPreparedSelectQueryPlan := true;
end;
DB.NoAJAXJSON := not (optForceAjaxJson in Options);
end;
function TDDDRestSettings.NewRestServerDB(const aDBFileName: TFileName;
const aModelRoot: RawUTF8; const aModelTables: array of TSQLRecordClass;
aOptions: TDDDRestSettingsOptions; aCacheSize: cardinal): TSQLRestServerDB;
begin
result := TSQLRestServerDB.CreateWithOwnModel(aModelTables, DefaultDataFolder +
UTF8ToString(DefaultDataFileName) + aDBFileName, false, aModelRoot, '', aCacheSize);
RestServerDBSetOptions(result, Options); // tune internal SQlite3 engine
result.Options := result.Options+[rsoNoInternalState];
result.CreateMissingTables;
end;
procedure TDDDRestSettings.WrapperGenerate(Rest: TSQLRestServer; Port: integer;
const DestFile, Template: TFileName);
var dest: TFileName;
mus: RawUTF8;
begin
if (self = nil) or (Rest = nil) then
exit;
if DestFile = '' then
dest := ExeVersion.ProgramFilePath+'mORMotClient.asc' else
dest := DestFile;
mus := StringFromFile(WrapperTemplateFolderFixed(true)+Template);
FileFromString(WrapperFromModel(Rest,mus,'',Port),dest);
end;
function TDDDRestSettings.WrapperSourceFolderFixed: TFileName;
begin
if fWrapperSourceFolders='' then
result := '' else begin
if fWrapperSourceFolderFixed='' then
fWrapperSourceFolderFixed := IncludeTrailingPathDelimiter(StringReplace(
fWrapperSourceFolders,'/',PathDelim,[rfReplaceAll]));
result := fWrapperSourceFolders;
end;
end;
function TDDDRestSettings.WrapperTemplateFolderFixed(ReturnLocalIfNoneSet: boolean): TFileName;
begin
if fWrapperTemplateFolder='' then
if ReturnLocalIfNoneSet then
result := ExeVersion.ProgramFilePath
else
result := '' else begin