]> Dogcows Code - chaz/homebank/blob - src/ext.c
add plugin engine (supports C and Perl plugins)
[chaz/homebank] / src / ext.c
1
2 #include <stdarg.h>
3 #include <string.h>
4 #include <gmodule.h>
5 #include <gtk/gtk.h>
6
7 #include "ext.h"
8
9 extern struct Preferences *PREFS;
10
11
12 const int _hook_recursion_soft_limit = 50;
13 const int _hook_recursion_hard_limit = 99;
14
15
16 struct PluginEngine
17 {
18 const gchar* type;
19 PluginEngineInitializer init;
20 PluginEngineTerminator term;
21 PluginEngineFileChecker check_file;
22 PluginMetadataReader read_metadata;
23 PluginLoader load_plugin;
24 PluginUnloader unload_plugin;
25 PluginExecutor execute;
26 PluginHookCaller call_hook;
27 };
28
29
30 static GList* _engine_list = NULL;
31 static GHashTable* _loaded_plugins = NULL;
32
33
34 void ext_init(int* argc, char** argv[], char** env[])
35 {
36 GList *list = g_list_first(_engine_list);
37 while (list)
38 {
39 struct PluginEngine* engine = list->data;
40 engine->init(argc, argv, env);
41 list = g_list_next(list);
42 }
43 if (!_loaded_plugins) {
44 _loaded_plugins = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
45 }
46 }
47
48 void ext_term(void)
49 {
50 GList *list = g_list_first(_engine_list);
51 while (list)
52 {
53 struct PluginEngine* engine = list->data;
54 engine->term();
55 list = g_list_next(list);
56 }
57 g_list_free(_engine_list);
58 _engine_list = NULL;
59
60 if (_loaded_plugins) {
61 g_hash_table_unref(_loaded_plugins);
62 _loaded_plugins = NULL;
63 }
64 }
65
66 void ext_register(const gchar* type,
67 PluginEngineInitializer init,
68 PluginEngineTerminator term,
69 PluginEngineFileChecker check_file,
70 PluginMetadataReader read_metadata,
71 PluginLoader load_plugin,
72 PluginUnloader unload_plugin,
73 PluginExecutor execute,
74 PluginHookCaller call_hook)
75 {
76 struct PluginEngine* engine = g_malloc0(sizeof(struct PluginEngine));
77 engine->type = type;
78 engine->init = init;
79 engine->term = term;
80 engine->check_file = check_file;
81 engine->read_metadata = read_metadata;
82 engine->load_plugin = load_plugin;
83 engine->unload_plugin = unload_plugin;
84 engine->execute = execute;
85 engine->call_hook = call_hook;
86 _engine_list = g_list_append(_engine_list, engine);
87 }
88
89
90 static struct PluginEngine* _get_engine_for_plugin(const gchar* plugin_filename)
91 {
92 if (!plugin_filename) {
93 return NULL;
94 }
95
96 GList *list = g_list_first(_engine_list);
97 while (list) {
98 struct PluginEngine* engine = list->data;
99 if (engine->check_file(plugin_filename)) {
100 return engine;
101 }
102 list = g_list_next(list);
103 }
104 return NULL;
105 }
106
107 static void _read_directory(const gchar* directory, GHashTable* hash)
108 {
109 GDir* dir = g_dir_open(directory, 0, NULL);
110 if (!dir) return;
111
112 const gchar* filename;
113 while ((filename = g_dir_read_name(dir))) {
114 gchar* full = g_build_filename(directory, filename, NULL);
115 if (g_file_test(full, G_FILE_TEST_IS_REGULAR) && _get_engine_for_plugin(filename)) {
116 g_hash_table_insert(hash, g_strdup(filename), NULL);
117 }
118 g_free(full);
119 }
120 g_dir_close(dir);
121 }
122
123 gchar** ext_list_plugins()
124 {
125 GHashTable* hash = g_hash_table_new(g_str_hash, g_str_equal);
126
127 gchar** it;
128 for (it = PREFS->ext_path; it && *it; ++it) {
129 _read_directory(*it, hash);
130 }
131
132 GList* list = g_list_sort(g_hash_table_get_keys(hash), (GCompareFunc)g_utf8_collate);
133 g_hash_table_unref(hash);
134
135 guint len = g_list_length(list);
136 gchar** strv = g_new0(gchar**, len + 1);
137 int i;
138 for (i = 0; i < len; ++i) {
139 strv[i] = g_list_nth_data(list, i);
140 }
141 g_list_free(list);
142
143 return strv;
144 }
145
146 gchar* ext_find_plugin(const gchar* plugin_filename)
147 {
148 if (!plugin_filename) return NULL;
149
150 gchar** it;
151 for (it = PREFS->ext_path; *it; ++it) {
152 if (!g_path_is_absolute(*it)) continue;
153
154 gchar* full = g_build_filename(*it, plugin_filename, NULL);
155 if (g_file_test(full, G_FILE_TEST_IS_REGULAR)) {
156 return full;
157 }
158 g_free(full);
159 }
160
161 return NULL;
162 }
163
164 GHashTable* ext_read_plugin_metadata(const gchar* plugin_filename)
165 {
166 gchar* full = ext_find_plugin(plugin_filename);
167 if (!full) return NULL;
168
169 GHashTable* ret = NULL;
170
171 struct PluginEngine* engine = _get_engine_for_plugin(plugin_filename);
172 if (engine && engine->read_metadata) {
173 ret = engine->read_metadata(full);
174 }
175
176 g_free(full);
177 return ret;
178 }
179
180 gint ext_load_plugin(const gchar* plugin_filename)
181 {
182 gchar* full = ext_find_plugin(plugin_filename);
183 if (!full) return -1;
184
185 gint ret = -1;
186
187 struct PluginEngine* engine = _get_engine_for_plugin(plugin_filename);
188 if (engine && engine->load_plugin && engine->load_plugin(full) == 0) {
189 g_hash_table_insert(_loaded_plugins, g_strdup(plugin_filename), NULL);
190 ret = 0;
191 }
192
193 g_free(full);
194 return ret;
195 }
196
197 void ext_unload_plugin(const gchar* plugin_filename)
198 {
199 gchar* full = ext_find_plugin(plugin_filename);
200 if (!full) return;
201
202 struct PluginEngine* engine = _get_engine_for_plugin(plugin_filename);
203 if (engine && engine->unload_plugin) {
204 engine->unload_plugin(full);
205 }
206
207 g_free(full);
208 g_hash_table_remove(_loaded_plugins, plugin_filename);
209 }
210
211 gboolean ext_is_plugin_loaded(const gchar* plugin_filename)
212 {
213 return g_hash_table_contains(_loaded_plugins, plugin_filename);
214 }
215
216 void ext_execute_action(const gchar* plugin_filename)
217 {
218 gchar* full = ext_find_plugin(plugin_filename);
219 if (!full) return;
220
221 struct PluginEngine* engine = _get_engine_for_plugin(plugin_filename);
222 if (engine && engine->execute) {
223 engine->execute(full);
224 }
225
226 g_free(full);
227 }
228
229 void ext_hook(const gchar* hook_id, ...)
230 {
231 GList *list = NULL;
232
233 va_list ap;
234 va_start(ap, hook_id);
235 for (;;) {
236 GValue* val = (GValue*)va_arg(ap, GValue*);
237 if (!val) break;
238 list = g_list_append(list, val);
239 }
240 va_end(ap);
241
242 ext_vhook(hook_id, list);
243 g_list_free(list);
244 }
245
246 void ext_vhook(const gchar* hook_id, GList* args)
247 {
248 static int recursion_level = 0;
249
250 if (_hook_recursion_hard_limit <= recursion_level) {
251 return;
252 } else if (_hook_recursion_soft_limit <= recursion_level) {
253 int level = recursion_level;
254 recursion_level = -1;
255 GValue val_level = G_VALUE_INIT;
256 ext_hook("deep_hook_recursion", EXT_INT(&val_level, level), NULL);
257 recursion_level = level;
258 }
259
260 ++recursion_level;
261
262 g_print("ext_hook: %s (level %d)\n", hook_id, recursion_level);
263 GList *list = g_list_first(_engine_list);
264 while (list)
265 {
266 struct PluginEngine* engine = list->data;
267 engine->call_hook(hook_id, args);
268 list = g_list_next(list);
269 }
270
271 --recursion_level;
272 }
273
274 gboolean ext_has(const gchar* feature)
275 {
276 #ifdef OFX_ENABLE
277 if (0 == g_utf8_collate(feature, "libofx")) {
278 return TRUE;
279 }
280 #endif
281 #ifdef PERL_ENABLE
282 if (0 == g_utf8_collate(feature, "perl")) {
283 return TRUE;
284 }
285 #endif
286 return FALSE;
287 }
288
289
290 void* ext_symbol_lookup(const gchar* symbol)
291 {
292 static GModule* module = NULL;
293 if (!module) module = g_module_open(NULL, 0);
294
295 void* ptr;
296 if (module && g_module_symbol(module, symbol, &ptr)) {
297 return ptr;
298 }
299
300 return NULL;
301 }
302
303
304 void ext_run_modal(const gchar* title, const gchar* text, const gchar* type)
305 {
306 GtkMessageType t = GTK_MESSAGE_INFO;
307 if (0 == g_utf8_collate(type, "error")) {
308 t = GTK_MESSAGE_ERROR;
309 }
310 if (0 == g_utf8_collate(type, "warn")) {
311 t = GTK_MESSAGE_WARNING;
312 }
313 if (0 == g_utf8_collate(type, "question")) {
314 t = GTK_MESSAGE_QUESTION;
315 }
316
317 GtkWidget* dialog = gtk_message_dialog_new(NULL,
318 GTK_DIALOG_DESTROY_WITH_PARENT, t,
319 GTK_BUTTONS_CLOSE, "%s", text);
320 if (title) {
321 gtk_window_set_title(GTK_WINDOW(dialog), title);
322 }
323
324 gtk_dialog_run(GTK_DIALOG(dialog));
325 gtk_widget_destroy(dialog);
326 }
327
This page took 0.048278 seconds and 4 git commands to generate.