]> Dogcows Code - chaz/homebank/blob - src/ext-native.c
add plugin engine (supports C and Perl plugins)
[chaz/homebank] / src / ext-native.c
1
2 #include <glib/gstdio.h>
3 #include <gmodule.h>
4
5 #include "ext.h"
6
7
8 static gint ext_native_init(int* argc, char** argv[], char** env[]);
9 static void ext_native_term(void);
10 static gboolean ext_native_check_file(const gchar* plugin_filename);
11 static GHashTable* ext_native_read_plugin_metadata(const gchar* plugin_filepath);
12 static gint ext_native_load_plugin(const gchar* plugin_filepath);
13 static void ext_native_unload_plugin(const gchar* plugin_filepath);
14 static void ext_native_execute_action(const gchar* plugin_filepath);
15 static void ext_native_call_hook(const gchar* hook_id, GList* args);
16
17 static gchar* _read_data_for_keyword(const gchar* keyword, const gchar* bytes, gsize len);
18
19
20 static GHashTable* _loaded_plugins = NULL;
21
22
23 static gint ext_native_init(int* argc, char** argv[], char** env[])
24 {
25 if (!_loaded_plugins) {
26 _loaded_plugins = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_module_close);
27 }
28 return 0;
29 }
30
31 static void ext_native_term(void)
32 {
33 if (_loaded_plugins) {
34 ext_native_call_hook("unload", NULL);
35 g_hash_table_unref(_loaded_plugins);
36 _loaded_plugins = NULL;
37 }
38 }
39
40 static gboolean ext_native_check_file(const gchar* plugin_filename)
41 {
42 if (g_str_has_suffix(plugin_filename, "."G_MODULE_SUFFIX)) {
43 return TRUE;
44 }
45 if (g_str_has_suffix(plugin_filename, ".la")) {
46 // allow a .la file only if no actual native plugin is found
47 gboolean check = FALSE;
48 gchar* copy = g_strdup(plugin_filename);
49 gchar* ext = g_strrstr(copy, ".la");
50 if (ext) {
51 *ext = '\0';
52 gchar* native_filename = g_strconcat(copy, "."G_MODULE_SUFFIX, NULL);
53 gchar* native_filepath = ext_find_plugin(native_filename);
54 check = !native_filepath;
55 g_free(native_filepath);
56 g_free(native_filename);
57 }
58 g_free(copy);
59 return check;
60 }
61 return FALSE;
62 }
63
64 static GHashTable* ext_native_read_plugin_metadata(const gchar* plugin_filepath)
65 {
66 GMappedFile* file = g_mapped_file_new(plugin_filepath, FALSE, NULL);
67 if (!file) {
68 g_printerr("mapping plugin file at %s failed\n", plugin_filepath);
69 return NULL;
70 }
71
72 gchar* bytes = g_mapped_file_get_contents(file);
73 gsize len = g_mapped_file_get_length(file);
74 if (len == 0 || !bytes) {
75 g_mapped_file_unref(file);
76 g_printerr("no data in plugin file at %s failed\n", plugin_filepath);
77 return NULL;
78 }
79
80 GHashTable* table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
81
82 const gchar* keywords[] = { "name", "version", "abstract", "author", "website", NULL };
83 const gchar** it;
84 for (it = keywords; *it; ++it) {
85 gchar* value = _read_data_for_keyword(*it, bytes, len);
86 g_hash_table_insert(table, g_strdup(*it), value);
87 }
88
89 g_mapped_file_unref(file);
90
91 return table;
92 }
93
94 static gint ext_native_load_plugin(const gchar* plugin_filepath)
95 {
96 if (g_hash_table_contains(_loaded_plugins, plugin_filepath)) {
97 return 0;
98 }
99
100 GModule* module = g_module_open(plugin_filepath, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
101 if (!module) {
102 g_printerr("Could not load native plugin: %s\n", g_module_error());
103 return -1;
104 }
105
106 g_hash_table_insert(_loaded_plugins, g_strdup(plugin_filepath), module);
107
108 void (*symbol)();
109 if (g_module_symbol(module, "load", (gpointer)&symbol)) {
110 symbol();
111 }
112
113 return 0;
114 }
115
116 static void ext_native_unload_plugin(const gchar* plugin_filepath)
117 {
118 GModule* module = g_hash_table_lookup(_loaded_plugins, plugin_filepath);
119 if (module) {
120 void (*symbol)();
121 if (g_module_symbol(module, "unload", (gpointer)&symbol)) {
122 symbol();
123 }
124 }
125
126 g_hash_table_remove(_loaded_plugins, plugin_filepath);
127 }
128
129 static void ext_native_execute_action(const gchar* plugin_filepath)
130 {
131 GModule* module = g_hash_table_lookup(_loaded_plugins, plugin_filepath);
132 if (module) {
133 void (*symbol)();
134 if (g_module_symbol(module, "execute", (gpointer)&symbol)) {
135 symbol();
136 }
137 }
138 }
139
140 static void ext_native_call_hook(const gchar* hook_id, GList* args)
141 {
142 gchar* symbol_name = g_strconcat("on_", hook_id, NULL);
143 void (*symbol)(GList*);
144
145 GHashTableIter it;
146 g_hash_table_iter_init(&it, _loaded_plugins);
147 GModule* module;
148
149 while (g_hash_table_iter_next(&it, NULL, (gpointer*)&module)) {
150 if (g_module_symbol(module, symbol_name, (gpointer)&symbol)) {
151 symbol(args);
152 }
153 }
154
155 g_free(symbol_name);
156 }
157
158
159 static gchar* _read_data_for_keyword(const gchar* keyword, const gchar* bytes, gsize len)
160 {
161 gchar* value = NULL;
162
163 gchar* pattern = g_strdup_printf("[\\x00\\t\\n ]%s\\s*[=:]\\s*([^\\x00]+)", keyword);
164 GRegex* r = g_regex_new(pattern, G_REGEX_CASELESS, 0, NULL);
165 g_free(pattern);
166
167 GMatchInfo* match = NULL;
168 if (g_regex_match_full(r, bytes, len, 0, 0, &match, NULL)) {
169 value = g_match_info_fetch(match, 1);
170 }
171
172 g_match_info_free(match);
173 g_regex_unref(r);
174
175 return value;
176 }
177
178
179 static void _register(void) __attribute__((constructor));
180 static void _register()
181 {
182 ext_register("native",
183 ext_native_init,
184 ext_native_term,
185 ext_native_check_file,
186 ext_native_read_plugin_metadata,
187 ext_native_load_plugin,
188 ext_native_unload_plugin,
189 ext_native_execute_action,
190 ext_native_call_hook);
191 }
192
This page took 0.046997 seconds and 4 git commands to generate.