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