1 module dscord.bot.plugin;
2 
3 import std.path,
4        std.file;
5 
6 import std.experimental.logger,
7        vibe.d : runTask;
8 
9 import dscord.client,
10        dscord.bot.command,
11        dscord.bot.listener,
12        dscord.bot.bot,
13        dscord.util.storage;
14 
15 /**
16   PluginOptions is a class that can be used to configure the base functionality
17   and utilties in use by a plugin.
18 */
19 class PluginOptions {
20   /** Does this plugin load/require a configuration file? */
21   bool useConfig = true;
22 
23   /** Does this plugin load/require a JSON storage file? */
24   bool useStorage = true;
25 }
26 
27 /**
28   PluginState is a class the encapsulates all run-time state required for a
29   plugin to exist. It's purpose is to allow for hot-reloading and replacing
30   of plugin code, without destroy/rebuilding run-time data.
31 */
32 class PluginState {
33   /** Plugin JSON Storage file (for data) */
34   Storage        storage;
35 
36   /** Plugin JSON Config file */
37   Storage        config;
38 
39   /** PluginOptions struct **/
40   PluginOptions  options;
41 
42   this(Plugin plugin, PluginOptions opts) {
43     this.options = opts ? opts : new PluginOptions;
44     this.storage = new Storage(plugin.storagePath);
45     this.config = new Storage(plugin.configPath);
46   }
47 }
48 
49 /**
50   A Plugin represents a modular, extendable class that encapsulates certain
51   Bot functionality into a logical slice. Plugins usually have a set of commands
52   and listeners attached to them, and are built to be dynamically loaded/reloaded
53   into a Bot.
54 */
55 class Plugin {
56   /** Bot instance for this plugin. Should always be set */
57   Bot     bot;
58 
59   /** Current runtime state for this plugin */
60   PluginState  state;
61 
62   mixin Listenable;
63   mixin Commandable;
64 
65   /**
66     The path to the dynamic library this plugin was loaded from. If set, this
67     signals this Plugin was loaded from a dynamic library, and can be reloaded
68     from the given path.
69   */
70   string dynamicLibraryPath;
71 
72   /**
73     Pointer to the dynamic library, used for cleaning up on shutdown.
74   */
75   void* dynamicLibrary;
76 
77   /**
78     Constructor for initial load. Usually called from the inherited constructor.
79   */
80   this(this T)(PluginOptions opts = null) {
81     this.state = new PluginState(this, opts);
82 
83     this.loadCommands!T();
84     this.loadListeners!T();
85   }
86 
87   /**
88     Plugin log instance.
89   */
90   @property Logger log() {
91     return this.bot.log;
92   }
93 
94   /**
95     Used to load the Plugin, initially loading state if requred.
96   */
97   void load(Bot bot, PluginState state = null) {
98     this.bot = bot;
99 
100     // Make sure our storage directory exists
101     if (!exists(this.storageDirectoryPath)) {
102       mkdir(this.storageDirectoryPath);
103     }
104 
105     // If we got state, assume this was a plugin reload and replace
106     if (state) {
107       this.state = state;
108     } else {
109       // If plugin uses storage, load the storage from disk
110       if (this.options.useStorage) {
111         this.storage.load();
112       }
113 
114       // If plugin uses config, load the config from disk
115       if (this.options.useConfig) {
116         this.config.load();
117       }
118     }
119   }
120 
121   /**
122     Used to unload the Plugin. Saves config/storage if required.
123   */
124   void unload(Bot bot) {
125     if (this.options.useStorage) {
126       this.storage.save();
127     }
128 
129     if (this.options.useConfig) {
130       this.config.save();
131     }
132   }
133 
134   /**
135     Returns path to this plugins storage directory.
136   */
137   @property string storageDirectoryPath() {
138     return "storage" ~ dirSeparator ~ this.name;
139   }
140 
141   /**
142     Returns path to this plugins storage file.
143   */
144   @property string storagePath() {
145     return this.storageDirectoryPath ~ dirSeparator ~ "storage.json";
146   }
147 
148   /**
149     Returns path to this plugins config file.
150   */
151   @property string configPath() {
152     return "config" ~ dirSeparator ~ this.name ~ ".json";
153   }
154 
155   /**
156    Storage instance for the plugin.
157   */
158   @property Storage storage() {
159     return this.state.storage;
160   }
161 
162   /**
163     Config instance for the plugin.
164    */
165   @property Storage config() {
166     return this.state.config;
167   }
168 
169   /**
170     PluginOptions instance for the plugin.
171   */
172   @property PluginOptions options() {
173     return this.state.options;
174   }
175 
176   /**
177     Client instance for the plugin.
178   */
179   @property Client client() {
180     return this.bot.client;
181   }
182 
183   /**
184     Returns the name of this plugin.
185   */
186   string name() {
187     return typeof(this).toString;
188   }
189 }