1 /** 2 * The module implements application skeleton 3 * 4 * Copyright: (c) 2015-2017, Milofon Project. 5 * License: Subject to the terms of the BSD license, as written in the included LICENSE.txt file. 6 * Authors: Maksim Galanin 7 */ 8 module dango.system.application; 9 10 public 11 { 12 import uniconf.core : UniConf; 13 14 import dango.system.exception; 15 import dango.system.logging; 16 import dango.system.plugin : SemVer; 17 } 18 19 private 20 { 21 import std.format : fmt = format; 22 23 import vibe.core.log : logDiagnostic, registerLogger; 24 import vibe.core.file : existsFile, readFileUTF8; 25 import vibe.core.core : lowerPrivileges, runEventLoop; 26 27 import commandr : Option, Command; 28 import uniconf.core : loadConfig; 29 30 import dango.inject : DependencyContainer, existingInstance, registerContext, Inject; 31 import dango.system.logging.core : configureLogging; 32 import dango.system.plugin; 33 } 34 35 36 /** 37 * Интерфейс приложения 38 */ 39 interface Application 40 { 41 /** 42 * Свойство возвращает наименование приложения 43 */ 44 string name() const pure @safe nothrow; 45 46 /** 47 * Свойство возвращает версию приложения 48 */ 49 SemVer release() const pure @safe nothrow; 50 51 /** 52 * Функция загружает свойства из файла при помощи локального загрузчика 53 * Params: 54 * 55 * filePath = Путь до файла 56 * 57 * Returns: Объект свойств 58 */ 59 UniConf loadConfigFile(string filePath) @safe; 60 61 /** 62 * Возвращает глобальный объект настроек приложения 63 */ 64 const(UniConf) getConfig() const pure nothrow @safe; 65 66 /** 67 * Возвращает глобальный контейнер зависимостей 68 */ 69 DependencyContainer getContainer() @safe nothrow; 70 } 71 72 73 /** 74 * Делегат инициализации зависимостей 75 */ 76 alias DependencyBootstrap = void delegate(DependencyContainer cont, UniConf config) @safe; 77 78 /** 79 * Делегат инициализации плагинов 80 */ 81 alias PluginBootstrap = void delegate(PluginManager manager) @safe; 82 83 /** 84 * Делегат инициализации приложения 85 */ 86 alias ApplicationBootstrap = void delegate(DependencyContainer cont, UniConf config) @safe; 87 88 89 /** 90 * Реализация приложения 91 */ 92 class DangoApplication : Application, PluginContainer!ConsolePlugin 93 { 94 private @safe 95 { 96 string _applicationName; 97 string _applicationSummary; 98 SemVer _applicationVersion; 99 100 string[] _defaultConfigs; 101 UniConf _applicationConfig; 102 ConsolePlugin[] _plugins; 103 104 DependencyContainer _container; 105 PluginManager _pluginManager; 106 DependencyBootstrap[] _dependencyBootstraps; 107 ApplicationBootstrap[] _applicationBootstraps; 108 PluginBootstrap[] _pluginBootstraps; 109 } 110 111 /** 112 * Main application constructor 113 */ 114 this(string name, string _version, string summary) @safe 115 { 116 this(name, SemVer(_version), summary); 117 } 118 119 /** 120 * Main application constructor 121 */ 122 this(string name, SemVer _version, string summary) @safe 123 { 124 this._applicationVersion = _version; 125 this._applicationSummary = summary; 126 this._applicationName = name; 127 this._container = new DependencyContainer(); 128 this._pluginManager = new PluginManager(_container); 129 } 130 131 /** 132 * Свойство возвращает наименование приложения 133 */ 134 string name() const pure nothrow @safe 135 { 136 return _applicationName; 137 } 138 139 /** 140 * Свойство возвращает версию приложения 141 */ 142 SemVer release() const pure nothrow @safe 143 { 144 return _applicationVersion; 145 } 146 147 /** 148 * Функция загружает свойства из файла при помощи локального загрузчика 149 * Params: 150 * 151 * filePath = Путь до файла 152 * 153 * Returns: Объект свойств 154 */ 155 UniConf loadConfigFile(string filePath) @safe 156 { 157 if (existsFile(filePath)) 158 return loadConfig(filePath); 159 else 160 throw new DangoApplicationException( 161 fmt!"Config file '%s' not found"(filePath)); 162 } 163 164 /** 165 * Возвращает глобальный объект настроек приложения 166 */ 167 const(UniConf) getConfig() const pure nothrow @safe 168 { 169 return _applicationConfig; 170 } 171 172 /** 173 * Возвращает глобальный контейнер зависимостей 174 */ 175 DependencyContainer getContainer() @safe nothrow 176 { 177 return _container; 178 } 179 180 /** 181 * Добавить путь до файла конфигурации 182 * Params: 183 * 184 * filePath = Путь до файла 185 */ 186 void addDefaultConfigFile(string filePath) @safe nothrow 187 { 188 _defaultConfigs ~= filePath; 189 } 190 191 /** 192 * Регистрация плагина 193 * Params: 194 * plugin = Плагин для регистрации 195 */ 196 void collectPlugin(ConsolePlugin plugin) @safe nothrow 197 { 198 _plugins ~= plugin; 199 } 200 201 /** 202 * Добавить инициализатор зависимостей 203 */ 204 void addDependencyBootstrap(DependencyBootstrap bst) @safe nothrow 205 { 206 _dependencyBootstraps ~= bst; 207 } 208 209 /** 210 * Добавить инициализатор плагинов 211 */ 212 void addPluginBootstrap(PluginBootstrap bst) @safe nothrow 213 { 214 _pluginBootstraps ~= bst; 215 } 216 217 /** 218 * Добавить инициализатор приложения 219 */ 220 void addApplicationBootstrap(ApplicationBootstrap bst) @safe nothrow 221 { 222 _applicationBootstraps ~= bst; 223 } 224 225 /** 226 * Запуск приложения 227 * 228 * Params: 229 * args = Входящие параметры 230 * 231 * Returns: Код завершения работы приложения 232 */ 233 int run()(string[] args) @trusted 234 { 235 import commandr : parse; 236 237 initializationApplicationConfig(args); 238 initializeDependencies(_container, _applicationConfig); 239 240 configureLogging(_container, _applicationConfig, ®isterLogger); 241 242 logInfo("Start application %s (%s)", _applicationName, _applicationVersion); 243 244 _pluginManager.registerPluginContainer(this); 245 foreach (bootstrap; _pluginBootstraps) 246 bootstrap(_pluginManager); 247 _pluginManager.initializePlugins(); 248 249 auto prog = new Program(_applicationName) 250 .version_(_applicationVersion.toString) 251 .summary(_applicationSummary); 252 253 foreach (ConsolePlugin plug; _plugins) 254 plug.registerCommand(prog); 255 256 auto progArgs = prog.parse(args); 257 258 foreach (bootstrap; _applicationBootstraps) 259 bootstrap(_container, _applicationConfig); 260 261 foreach (ConsolePlugin plug; _plugins) 262 { 263 if (auto ret = plug.runCommand(progArgs)) 264 return ret; 265 } 266 267 return 0; 268 } 269 270 271 private: 272 273 274 void initializeDependencies(DependencyContainer container, UniConf config) @safe 275 { 276 container.register!(Application, typeof(this)).existingInstance(this); 277 container.registerContext!LoggingContext(); 278 foreach (bootstrap; _dependencyBootstraps) 279 bootstrap(container, config); 280 } 281 282 283 void initializationApplicationConfig()(ref string[] args) @trusted 284 { 285 import std.getopt : getopt, arraySep, gconfig = config; 286 287 initializationConfigSystem(); 288 289 arraySep = ","; 290 string[] configFiles; 291 auto helpInformation = getopt(args, 292 gconfig.passThrough, 293 "c|config", &configFiles); 294 295 if (!configFiles.length) 296 configFiles = _defaultConfigs; 297 298 foreach (string cFile; configFiles) 299 { 300 auto config = loadConfigFile(cFile); 301 _applicationConfig = _applicationConfig ~ config; 302 } 303 } 304 305 306 void initializationConfigSystem()() @trusted 307 { 308 import uniconf.core : registerConfigLoader, setConfigReader; 309 310 setConfigReader((string path) { 311 return readFileUTF8(path); 312 }); 313 314 registerConfigLoader([".json"], (string data) { 315 import uniconf.json : parseJson; 316 return parseJson!UniConf(data); 317 }); 318 319 version (Have_uniconf_yaml) 320 registerConfigLoader([".yaml", ".yml"], (string data) { 321 import uniconf.yaml : parseYaml; 322 return parseYaml!UniConf(data); 323 }); 324 325 version (Have_uniconf_sdlang) 326 registerConfigLoader([".sdl"], (string data) { 327 import uniconf.sdlang : parseSDLang; 328 return parseSDLang!UniConf(data); 329 }); 330 } 331 } 332 333 334 /** 335 * Реализация плагина для запуска приложения в фоне 336 */ 337 class DaemonApplicationPlugin : PluginContainer!DaemonPlugin, ConsolePlugin 338 { 339 private @safe 340 { 341 DaemonPlugin[] _plugins; 342 } 343 344 /** 345 * Свойство возвращает наименование плагина 346 */ 347 string name() pure @safe nothrow 348 { 349 return "Daemon"; 350 } 351 352 /** 353 * Свойство возвращает описание плагина 354 */ 355 string summary() pure nothrow @safe 356 { 357 return "Daemon application"; 358 } 359 360 /** 361 * Свойство возвращает версию плагина 362 */ 363 SemVer release() pure nothrow @safe 364 { 365 return SemVer(0, 0, 1); 366 } 367 368 /** 369 * Регистрация плагина 370 * Params: 371 * plugin = Плагин для регистрации 372 */ 373 void collectPlugin(DaemonPlugin plug) @safe nothrow 374 { 375 _plugins ~= plug; 376 } 377 378 /** 379 * Register command 380 */ 381 void registerCommand(Program prog) @safe 382 { 383 auto comm = new Command("start", summary, release.toString) 384 .add(new Option("uid", "user", "Sets the user name for privilege lowering.")) 385 .add(new Option("gid", "group", "Sets the group name for privilege lowering.")); 386 prog.add(comm); 387 } 388 389 /** 390 * Run commnad 391 */ 392 int runCommand(ProgramArgs args) @trusted 393 { 394 import vibe.core.core : Timer, setTimer; 395 import core.time : seconds; 396 397 auto cmd = args.command(); 398 int ret = 0; 399 if (cmd is null || cmd.name != "start") 400 return ret; 401 402 foreach (DaemonPlugin dp; _plugins) 403 { 404 ret = dp.startDaemon(); 405 if (ret) 406 return ret; 407 } 408 409 string uid = cmd.option("user"); 410 string gid = cmd.option("group"); 411 lowerPrivileges(uid, gid); 412 413 void emptyTimer() {} 414 auto timer = setTimer(1.seconds, &emptyTimer, true); 415 416 logDiagnostic("Running event loop..."); 417 ret = runEventLoop(); 418 logDiagnostic("Event loop exited with status %d.", ret); 419 420 foreach (DaemonPlugin dp; _plugins) 421 { 422 ret = dp.stopDaemon(ret); 423 if (ret) 424 return ret; 425 } 426 427 return ret; 428 } 429 } 430