1 /*
2 Copyright (c) 2023-2024 Andrea Fontana
3 
4 Permission is hereby granted, free of charge, to any person
5 obtaining a copy of this software and associated documentation
6 files (the "Software"), to deal in the Software without
7 restriction, including without limitation the rights to use,
8 copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the
10 Software is furnished to do so, subject to the following
11 conditions:
12 
13 The above copyright notice and this permission notice shall be
14 included in all copies or substantial portions of the Software.
15 
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 OTHER DEALINGS IN THE SOFTWARE.
24 */
25 
26 module serverino.main;
27 
28 import serverino;
29 
30 import std.experimental.logger : Logger, LogLevel;
31 import std.stdio : File, stderr;
32 import std.compiler : version_minor;
33 
34 // This is a custom logger for the serverino application
35 class ServerinoLogger : Logger
36 {
37    static if (version_minor > 100)
38    {
39       this(LogLevel lv) shared
40       {
41          super(lv);
42       }
43    }
44    else {
45       this(LogLevel lv)
46       {
47          super(lv);
48       }
49    }
50 
51    @trusted
52    override void writeLogMsg(ref LogEntry payload)
53    {
54       import std.process : environment, thisProcessID;
55       import std.conv : to;
56 
57       static immutable LLSTR = [
58          LogLevel.all : "[\x1b[1ml\x1b[0m]", LogLevel.trace : "[\x1b[1;32mt\x1b[0m]",
59          LogLevel.info : "[\x1b[1;32mi\x1b[0m]", LogLevel.warning : "[\x1b[1;33mw\x1b[0m]",
60          LogLevel.critical : "[\x1b[1;31mc\x1b[0m]", LogLevel.fatal : "[\x1b[1;31mf\x1b[0m]",
61       ];
62 
63       import std.path : baseName;
64       import std.conv : text;
65       import std.format : format;
66 
67       string t = payload.timestamp.toISOExtString;
68       string msg = payload.msg;
69 
70       if(payload.logLevel >= LogLevel.critical)
71          msg = "\x1b[1;31m" ~ msg ~ "\x1b[0m";
72 
73       string intro;
74 
75       if (environment.get("SERVERINO_DAEMON") == null)
76       {
77          version(Windows){ intro = "\x1b[1m-\x1b[0m "; }
78          else { intro = "\x1b[1m★\x1b[0m "; }
79       }
80       else {
81          import std.stdio : write, stderr;
82          import std.string : indexOf;
83 
84          immutable size_t seed = thisProcessID;
85 
86          auto r = ((seed*123467983)%15+1)     * 255/15;
87          auto g = ((r*seed*123479261)%15+1)   * 255/15;
88          auto b = ((g*seed*123490957)%15+1)   * 255/15;
89 
90          version(Windows){ intro = text("\x1b[38;2;", r, ";", g, ";", b,"mD\x1b[0m "); }
91          else { intro = text("\x1b[38;2;", r, ";", g, ";", b,"m■\x1b[0m "); }
92 
93       }
94 
95       intro ~= format("[%06d]", thisProcessID);
96       auto str = text(intro, " ", LLSTR[payload.logLevel], " \x1b[1m", t[0..10]," ", t[11..16], "\x1b[0m ", "[", baseName(payload.file),":",format("%04d", payload.line), "] ", msg, "\n");
97       stderr.write(str);
98    }
99 
100    private File outputStream;
101 }
102 
103 // This is the main entry point for the serverino application
104 template ServerinoMain(Modules...)
105 {
106    mixin ServerinoLoop!Modules;
107 
108    int main(string[] args)
109    {
110       return mainServerinoLoop(args);
111    }
112 }
113 
114 // This is the main loop for the serverino application
115 template ServerinoLoop(Modules...)
116 {
117    import std.meta : AliasSeq;
118    import std.traits : moduleName;
119 
120    alias allModules = AliasSeq!(mixin(moduleName!mainServerinoLoop), Modules);
121 
122    static assert (moduleName!mainServerinoLoop != "main", "Please, don't use `main` as module name.");
123 
124    static foreach(m; allModules)
125       static assert(__traits(isModule, m), "All ServerinoMain params must be modules");
126 
127    int mainServerinoLoop(string[] args)
128    {
129       import std.traits : getSymbolsByUDA, isFunction, ReturnType, Parameters;
130       ServerinoConfig config = ServerinoConfig.create();
131 
132       // Call ServerinoConfig func(); or ServerinoConfig func(args);
133       static foreach(m; allModules)
134       {
135          static foreach(f; getSymbolsByUDA!(m, onServerInit))
136          {
137             static assert(isFunction!f, "`" ~ __traits(identifier, f) ~ "` is marked with @onServerInit but it is not a function");
138             static assert(is(ReturnType!f == ServerinoConfig), "`" ~ __traits(identifier, f) ~ "` is " ~ ReturnType!f.toString ~ " but should be `ServerinoConfig`");
139 
140             static if (is(Parameters!f == AliasSeq!(string[])))  config = f(args);
141             else static if (is(Parameters!f == AliasSeq!()))  config = f();
142             else static assert(0, "`" ~ __traits(identifier, f) ~ "` is marked with @onServerInit but it is not callable");
143 
144             static if (!__traits(compiles, hasSetup)) { enum hasSetup; }
145             else static assert(0, "You can't mark more than one function with @onServerInit");
146          }
147       }
148 
149       return wakeServerino!allModules(config);
150    }
151 }
152 
153 
154 int wakeServerino(Modules...)(ref ServerinoConfig config)
155 {
156    import std.experimental.logger.core;
157    import std.compiler : version_major;
158 
159    static if (version_minor > 100)
160       sharedLog = new shared ServerinoLogger(config.daemonConfig.logLevel);
161    else
162       sharedLog = new ServerinoLogger(config.daemonConfig.logLevel);
163 
164    if (config.returnCode != 0)
165       return config.returnCode;
166 
167    config.validate();
168 
169    DaemonConfigPtr daemonConfig = &config.daemonConfig;
170    WorkerConfigPtr workerConfig = &config.workerConfig;
171 
172    // Let's wake up the daemon or the worker
173    import serverino.daemon;
174    import serverino.worker;
175    import std.process : environment;
176 
177    if (environment.get("SERVERINO_DAEMON") == null) Daemon.instance.wake!Modules(daemonConfig);
178 	else Worker.instance.wake!Modules(workerConfig);
179 
180    return 0;
181 }