1 /**
2  * The module implements plugin system
3  *
4  * Copyright: (c) 2015-2020, Milofon Project.
5  * License: Subject to the terms of the BSD 3-Clause License, as written in the included LICENSE.md file.
6  * Author: <m.galanin@milofon.pro> Maksim Galanin
7  * Date: 2020-03-27
8  */
9 
10 module dango.system.plugin;
11 
12 public
13 {
14     import BrightProof : SemVer;
15     import commandr : Program, ProgramArgs;
16 }
17 
18 private
19 {
20     import std.traits;
21 
22     import dango.system.logging : logInfo;
23     import dango.inject : DependencyContainer, inject;
24     import dango.system.exception;
25 }
26 
27 
28 /**
29  * Интерфейс плагина
30  */
31 interface Plugin
32 {
33     /**
34      * Свойство возвращает наименование плагина
35      */
36     string name() pure @safe nothrow;
37 
38     /**
39      * Свойство возвращает версию приложения
40      */
41     SemVer release() pure @safe nothrow;
42 }
43 
44 
45 /**
46  * Интерфейс контейнера плагинов
47  */
48 interface PluginContainer(P : Plugin)
49 {
50     /**
51      * Обработка контейнером нового плагина
52      *
53      * Params:
54      * plugin = Плагин для добавления
55      */
56     void collectPlugin(P plugin) @safe;
57 }
58 
59 
60 /**
61  * Plugin context
62  */
63 interface PluginContext(P : Plugin, A...)
64 {
65     void registerPlugins(P plugin, A args) @safe;
66 }
67 
68 
69 /**
70  * Register plugin context
71  */
72 void registerContext(Context : PluginContext!(P, A), P : Plugin, A...)(P plugin, A args) @safe
73 {
74     auto ctx = new Context();
75     ctx.registerPlugins(plugin, args);
76 }
77 
78 
79 /**
80  * Плагин поддерживающий обработку консольных команд
81  */
82 interface ConsolePlugin : Plugin
83 {
84     /**
85      * Свойство возвращает описание плагина
86      */
87     string summary() pure @safe nothrow;
88 
89     /**
90      * Регистрация обработцика команды
91      *
92      * Params:
93      * prog = Объект программы
94      */
95     void registerCommand(Program prog) @safe;
96 
97     /**
98      * Получение параметров командной строки
99      *
100      * Params:
101      * pArgs = Обработчик аргументов
102      */
103     int runCommand(ProgramArgs pArgs) @safe;
104 }
105 
106 
107 /**
108  * Плагин поддерживающий работу в фоновом режиме
109  */
110 interface DaemonPlugin : Plugin
111 {
112     /**
113      * Запуск процесса
114      */
115     int startDaemon();
116 
117     /**
118      * Остановка процесса
119      *
120      * Params:
121      * exitStatus = Код завершения приложения
122      */
123     int stopDaemon(int exitStatus);
124 }
125 
126 
127 /**
128  * Менеджер плагинов
129  */
130 class PluginManager
131 {
132     private 
133     {
134         alias PluginCollect = void delegate(Plugin) @safe;
135 
136         struct PluginInfo
137         {
138             TypeInfo info;
139             Plugin plugin;
140         }
141 
142         DependencyContainer _dcontainer;
143         PluginCollect[][TypeInfo] _containers;
144         PluginInfo[] _registrations;
145     }
146 
147 
148     /**
149      * Main constructor
150      */
151     this(DependencyContainer container) @safe nothrow
152     {
153         this._dcontainer = container;
154     }
155 
156     /**
157      * Регистрация плагина
158      */
159     P registerPlugin(P : Plugin)() @safe
160         if (!is(P == Plugin))
161     {
162         P plugin = new P();
163         inject!P(_dcontainer, plugin);
164         return registerPlugin!P(plugin);
165     }
166 
167     /**
168      * Регистрация плагина
169      */
170     P registerPlugin(P : Plugin)(P plugin) @safe
171         if (!is(P == Plugin))
172     {
173         static foreach (PI; InterfacesTuple!P)
174         {
175             static if (is(PI : PluginContainer!I, I : Plugin))
176                 registerPluginContainer!PI(plugin);
177             static if (is(PI : Plugin) && !is(PI == Plugin))
178                 _registrations ~= PluginInfo(typeid(PI), plugin);
179         }
180         return plugin;
181     }
182 
183     /**
184      * Регистрация контейнера плагинов
185      */
186     void registerPluginContainer(C : PluginContainer!P, P)(C containerPlugin) @safe
187     {
188         _containers[typeid(P)] ~= delegate void(Plugin p) {
189                 containerPlugin.collectPlugin(cast(P)p);
190             };
191     }
192 
193     /**
194      * Инициализация плагинов
195      */
196     void initializePlugins() @trusted
197     {
198         foreach (reg; _registrations)
199         {
200             if (reg.plugin is null || reg.info is null)
201                 throw new DangoPluginException("Error registration plugin");
202 
203             if (auto collectors = reg.info in _containers)
204             {
205                 logInfo("Use plugin %s (%s) as %s", reg.plugin.name, reg.plugin.release, reg.info);
206                 foreach (collector; *collectors)
207                     collector(reg.plugin);
208             }
209             else
210                 throw new DangoPluginException("Error initialize plugin. "
211                         ~ "Not found container for plugin " ~ reg.info.toString);
212         }
213     }
214 }
215