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 }