Statistics
| Revision:

root / trunk / nntpgrab_core / plugins.c @ 1777

History | View | Annotate | Download (53.1 KB)

1
/* 
2
    Copyright (C) 2005-2010  Erik van Pienbroek
3

                
4
    This program is free software; you can redistribute it and/or modify
5
    it under the terms of the GNU General Public License as published by
6
    the Free Software Foundation; either version 2 of the License, or
7
    (at your option) any later version.
8

                
9
    This program is distributed in the hope that it will be useful,
10
    but WITHOUT ANY WARRANTY; without even the implied warranty of
11
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
    GNU General Public License for more details.
13

                
14
    You should have received a copy of the GNU General Public License
15
    along with this program; if not, write to the Free Software
16
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
*/
18

                
19
#include 
20
#include 
21
#include 
22
#include 
23
#include 
24

                
25
#include "nntpgrab.h"
26
#include "nntpgrab_plugin.h"
27
#include "nntpgrab_utils.h"
28
#include "nntpgrab_internal.h"
29
#include "plugins.h"
30
#include "marshalers.h"
31
#include "config.h"
32

                
33
#include "download_queue.h"
34
#include "download_thread.h"
35
#include "configuration.h"
36

                
37
static GList *all_available_plugins = NULL;
38
static GHashTable *all_plugin_events = NULL;
39
static GList *all_connected_events = NULL;
40
static NGPlugin *function_plugin_data = NULL;
41
//static NGPlugin *event_plugin_data = NULL;
42

                
43
static Configuration *config = NULL;
44

                
45
struct _connected_event {
46
    NGPlugin *plugin_data;
47
    char event_name[128];
48
    guint event_name_hash;
49
    NGPluginFunction impl;
50
};
51

                
52
typedef struct _ng_plugin_function_or_event_ptr {
53
    char function_or_event_name[128];
54
    NGPluginFunction impl;
55
    GSignalCMarshaller marshaller;
56
    GType return_type;
57
    int num_params;
58
    GType *params;
59
    gboolean is_plugin_event;
60
} NgPluginFunctionOrEventPtr;
61

                
62
typedef struct _ng_plugin_class {
63
    GObjectClass parent;
64
} NGPluginClass;
65

                
66
G_DEFINE_TYPE(NGPlugin, ng_plugin, G_TYPE_OBJECT);
67

                
68
#define NG_TYPE_PLUGIN              (ng_plugin_get_type ())
69
#define NG_PLUGIN(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), NG_TYPE_PLUGIN, NGPlugin))
70
#define NG_PLUGIN_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), NG_TYPE_PLUGIN, NGPluginClass))
71
#define IS_NG_PLUGIN(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), NG_TYPE_PLUGIN))
72
#define IS_NG_PLUGIN_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), NG_TYPE_PLUGIN))
73
#define NG_PLUGIN_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), NG_TYPE_PLUGIN, NGPluginClass))
74

                
75
NntpgrabCore *get_core(void);
76

                
77
static void plugins_set_emit_log_messages_to_plugins(ngboolean val);
78
static void config_changed(Configuration *obj, gpointer data);
79
static void plugin_loaded(NntpgrabCore *core, const char *plugin_name, gboolean is_persistent, gpointer data);
80

                
81
static NGList *
82
config_get_avail_servers_wrapper(void)
83
{
84
    g_return_val_if_fail(config != NULL, NULL);
85

                
86
    return (NGList*) configuration_get_avail_servers(config);
87
}
88

                
89
static void
90
config_free_avail_servers_wrapper(NGList *servers)
91
{
92
    g_return_if_fail(config != NULL);
93

                
94
    configuration_free_avail_servers(config, (GList *) servers);
95
}
96

                
97
static ngboolean
98
config_get_server_info_wrapper(const char *servername, NGConfigServer *server)
99
{
100
    NGConfigServer *tmp;
101

                
102
    g_return_val_if_fail(config != NULL, FALSE);
103
    g_return_val_if_fail(servername != NULL, FALSE);
104
    g_return_val_if_fail(server != NULL, FALSE);
105

                
106
    tmp = configuration_get_server_info(config, servername);
107
    if (!tmp) {
108
        return FALSE;
109
    }
110

                
111
    memcpy(server, tmp, sizeof(NGConfigServer));
112
    g_slice_free(NGConfigServer, tmp);
113

                
114
    return TRUE;
115
}
116

                
117
static ngboolean
118
config_add_server_wrapper(NGConfigServer new_server, char **errmsg)
119
{
120
    g_return_val_if_fail(config != NULL, FALSE);
121

                
122
    return configuration_add_server(config, new_server, errmsg);
123
}
124

                
125
static ngboolean
126
config_del_server_wrapper(const char *servername, char **errmsg)
127
{
128
    g_return_val_if_fail(servername != NULL, FALSE);
129
    g_return_val_if_fail(config != NULL, FALSE);
130

                
131
    return configuration_del_server(config, servername, errmsg);
132
}
133

                
134
static ngboolean
135
config_edit_server_wrapper(const char *servername, NGConfigServer server, char **errmsg)
136
{
137
    g_return_val_if_fail(config != NULL, FALSE);
138

                
139
    return configuration_edit_server(config, servername, server, errmsg);
140
}
141

                
142
static NGConfigOpts
143
config_get_opts_wrapper(void)
144
{
145
    NGConfigOpts nil;
146
    memset(&nil, 0, sizeof(nil));
147
    g_return_val_if_fail(config != NULL, nil);
148

                
149
    return configuration_get_opts(config);
150
}
151

                
152
static void
153
config_set_opts_wrapper(NGConfigOpts opts)
154
{
155
    g_return_if_fail(config != NULL);
156

                
157
    configuration_set_opts(config, opts);
158
}
159

                
160
static ngboolean
161
config_save_wrapper(char **errmsg)
162
{
163
    g_return_val_if_fail(config != NULL, FALSE);
164

                
165
    return configuration_save(config, errmsg);
166
}
167

                
168
static ngboolean
169
schedular_start_wrapper(void)
170
{
171
    g_return_val_if_fail(config != NULL, FALSE);
172

                
173
    return download_thread_start(config);
174
}
175

                
176
static ngboolean
177
schedular_stop_wrapper(const char *reason, ngboolean wait)
178
{
179
    if (wait) {
180
        return download_thread_stop(FALSE, reason);
181
    } else {
182
        download_thread_abort_without_waiting("%s", (reason ? reason : ""));
183
        return TRUE;
184
    }
185
}
186

                
187
static void
188
server_request_quit(void)
189
{
190
    NntpgrabCore *core = get_core();
191

                
192
    if (!core) {
193
        return;
194
    }
195

                
196
    g_signal_emit_by_name(core, "quit_requested");
197
}
198

                
199
static void
200
ng_plugin_init(NGPlugin *obj)
201
{
202
    obj->core_data = g_slice_new0(NGPluginCoreData);
203

                
204
    memset(&obj->core_funcs, 0, sizeof(obj->core_funcs));
205

                
206
    obj->core_funcs.config_get_avail_servers = config_get_avail_servers_wrapper;
207
    obj->core_funcs.config_free_avail_servers = config_free_avail_servers_wrapper;
208
    obj->core_funcs.config_get_server_info = config_get_server_info_wrapper;
209
    obj->core_funcs.config_add_server = config_add_server_wrapper;
210
    obj->core_funcs.config_del_server = config_del_server_wrapper;
211
    obj->core_funcs.config_edit_server = config_edit_server_wrapper;
212
    obj->core_funcs.config_get_opts = config_get_opts_wrapper;
213
    obj->core_funcs.config_set_opts = config_set_opts_wrapper;
214
    obj->core_funcs.config_save = config_save_wrapper;
215
    obj->core_funcs.schedular_start = schedular_start_wrapper;
216
    obj->core_funcs.schedular_stop = schedular_stop_wrapper;
217
    obj->core_funcs.schedular_get_state = download_thread_get_state;
218
    obj->core_funcs.schedular_add_file_to_queue = download_queue_add_file_to_queue;
219
    obj->core_funcs.schedular_del_file_from_queue = download_queue_del_file_from_queue;
220
    obj->core_funcs.schedular_restart_file = download_queue_restart_file;
221
    obj->core_funcs.schedular_save_queue = download_queue_save;
222
    obj->core_funcs.schedular_foreach_file = download_queue_foreach_file;
223
    obj->core_funcs.schedular_move_file = download_queue_move_file;
224
    obj->core_funcs.schedular_move_collection = download_queue_move_collection;
225
    obj->core_funcs.schedular_mark_task_optional = download_queue_mark_task_optional;
226
    obj->core_funcs.plugins_get_avail_plugins = plugins_get_avail_plugins;
227
    obj->core_funcs.plugins_free_avail_plugins = plugins_free_avail_plugins;
228
    obj->core_funcs.plugins_get_plugin_info = plugins_get_plugin_info;
229
    obj->core_funcs.plugins_load_plugin = plugins_load_plugin_by_name;
230
    obj->core_funcs.plugins_unload_plugin = plugins_unload_plugin_by_name;
231
    obj->core_funcs.plugins_set_persistent = plugins_set_persistent;
232
    obj->core_funcs.server_request_quit = server_request_quit;
233
    obj->core_funcs.set_emit_log_messages = plugins_set_emit_log_messages_to_plugins;
234
}
235

                
236
static void
237
ng_plugin_finalize(GObject *obj)
238
{
239
    NGPlugin *plugin_data = NG_PLUGIN(obj);
240
    GList *list;
241

                
242
    list = plugin_data->core_data->exported_functions;
243
    while (list) {
244
        g_slice_free(NgPluginFunctionOrEventPtr, list->data);
245
        list = g_list_next(list);
246
    }
247
    g_list_free(plugin_data->core_data->exported_functions);
248
    plugin_data->core_data->exported_functions = NULL;
249

                
250
    list = plugin_data->core_data->exported_events;
251
    while (list) {
252
        g_slice_free(NgPluginFunctionOrEventPtr, list->data);
253
        list = g_list_next(list);
254
    }
255
    g_list_free(plugin_data->core_data->exported_events);
256
    plugin_data->core_data->exported_events = NULL;
257

                
258
    list = plugin_data->core_data->required_functions_and_events;
259
    while (list) {
260
        g_free(list->data);
261
        list = g_list_next(list);
262
    }
263
    g_list_free(plugin_data->core_data->required_functions_and_events);
264
    plugin_data->core_data->required_functions_and_events = NULL;
265

                
266
    if (plugin_data->core_data->plugin) {
267
#ifndef WIN32
268
        /* This function causes a crash on Win32 environments */
269
        g_module_close(plugin_data->core_data->plugin);
270
#endif
271
        plugin_data->core_data->plugin = NULL;
272
    }
273

                
274
    g_slice_free(NGPluginCoreData, plugin_data->core_data);
275
    plugin_data->core_data = NULL;
276
}
277

                
278
static void
279
ng_plugin_class_init(NGPluginClass *klass)
280
{
281
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
282

                
283
    gobject_class->finalize = ng_plugin_finalize;
284
}
285

                
286
static gboolean
287
bind_plugin_function(GModule *plugin, const char *function_name, gpointer *symbol)
288
{
289
    if (!g_module_symbol(plugin, function_name, symbol)) {
290
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_INFO, __FILE__ ":%i Unable to find function '%s' in plugin '%s'. Ignoring plugin", __LINE__, function_name, g_module_name(plugin));
291
        return FALSE;
292
    }
293

                
294
    return TRUE;
295
}
296

                
297
static void
298
init_internal_plugin(void)
299
{
300
    NGPlugin *plugin_data = g_object_new(NG_TYPE_PLUGIN, NULL);
301

                
302
    ng_plugin_set_name(plugin_data, "Internal");
303
    ng_plugin_set_author(plugin_data, "Erik van Pienbroek");
304
    ng_plugin_set_version(plugin_data, PACKAGE_VERSION);
305
    ng_plugin_set_url(plugin_data, "https://www.nntpgrab.nl");
306
    ng_plugin_set_description(plugin_data, "Plugin which provides internal NNTPGrab functions");
307

                
308
    ng_plugin_set_required_function(plugin_data, "decode_file");
309

                
310
    plugin_data->core_data->is_required = TRUE;
311

                
312
    all_available_plugins = g_list_append(all_available_plugins, plugin_data);
313
}
314

                
315
static void
316
try_to_init_plugin(const char *path, const char *filename)
317
{
318
    char *complete_path;
319
    GModule *plugin;
320
    NGPlugin *plugin_data;
321

                
322
#ifdef WIN32
323
#define EXT "-0.dll"
324
#else
325
#define EXT ".so"
326
#endif
327

                
328
    /* Verify the extension */
329
    if (!g_str_has_suffix(filename, EXT)) {
330
        /* Unknown extension, ignore */
331
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, __FILE__ ":%i file %s ignored", __LINE__, filename);
332
        return;
333
    }
334

                
335
    /* Try to load the plugin */
336
    complete_path = g_build_filename(path, filename, NULL);
337
    plugin = g_module_open(complete_path, G_MODULE_BIND_LOCAL);
338
    if (!plugin) {
339
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_WARNING, __FILE__ ":%i unable to load plugin %s: %s", __LINE__, complete_path, g_module_error());
340

                
341
        g_free(complete_path);
342
        return;
343
    }
344
    g_free(complete_path);
345

                
346
    /* Initialize the NGPlugin structure */
347
    plugin_data = g_object_new(NG_TYPE_PLUGIN, NULL);
348
    plugin_data->core_data->plugin = plugin;
349

                
350
    /* Mark plugins as required by default (if they turn out to be optional it will be updated automatically while determing the dependency order) */
351
    plugin_data->core_data->is_required = TRUE;
352

                
353
    /* Try to bind the functions which NNTPGrab plugins should export */
354
    if (!bind_plugin_function(plugin, "nntpgrab_plugin_initialize", (gpointer*) &plugin_data->core_data->init_func) ||
355
        !bind_plugin_function(plugin, "nntpgrab_plugin_load", (gpointer*) &plugin_data->core_data->load_func) ||
356
        !bind_plugin_function(plugin, "nntpgrab_plugin_unload", (gpointer*) &plugin_data->core_data->unload_func) ||
357
        !bind_plugin_function(plugin, "nntpgrab_plugin_destroy", (gpointer*) &plugin_data->core_data->destroy_func) ||
358
        !bind_plugin_function(plugin, "nntpgrab_plugin_can_unload", (gpointer*) &plugin_data->core_data->can_unload_func) ||
359
        !bind_plugin_function(plugin, "nntpgrab_plugin_get_version", (gpointer*) &plugin_data->core_data->get_version_func)) {
360

                
361
        g_object_unref(plugin_data);
362

                
363
        return;
364
    }
365

                
366
    /* Verify the API version */
367
    if (plugin_data->core_data->get_version_func() != NNTPGRAB_PLUGIN_API_VERSION) {
368
#ifdef WIN32
369
        /* The Win32 setup for NNTPGrab 0.6.90 contained an older version of the example plugin 
370
         * which causes a harmless error message. Suppress this error message with an ugly hack */
371
        if (strstr(g_module_name(plugin), "libnntpgrab_plugin_example-0.dll") && plugin_data->core_data->get_version_func() == 20091225) {
372
            /* Broken example plugin detected, ignore */
373
            g_object_unref(plugin_data);
374
            return;
375
        }
376
#endif
377

                
378
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_WARNING, __FILE__ ":%i unable to load plugin '%s' due to an API mismatch (plugin API version = %i, expected = %i)", __LINE__, g_module_name(plugin), plugin_data->core_data->get_version_func(), NNTPGRAB_PLUGIN_API_VERSION);
379

                
380
        g_object_unref(plugin_data);
381

                
382
        return;
383
    }
384

                
385
    /* Allow the plugin to indicate which functions and events are needed or exported */
386
    plugin_data->core_data->init_func(plugin_data);
387

                
388
    /* Plugin initialised succesfully */
389
    all_available_plugins = g_list_append(all_available_plugins, plugin_data);
390

                
391
    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, __FILE__ ":%i successfully loaded plugin '%s'", __LINE__, g_module_name(plugin));
392
}
393

                
394
static gboolean
395
load_plugin_base(NGPlugin *plugin_data, char **errmsg)
396
{
397
    g_return_val_if_fail(function_plugin_data != NULL, FALSE);
398
    g_return_val_if_fail(plugin_data != NULL, FALSE);
399
    g_return_val_if_fail(plugin_data->core_data->is_loaded == FALSE, FALSE);
400

                
401
    if (plugin_data->core_data->load_func &&
402
        !plugin_data->core_data->load_func(plugin_data, errmsg)) {
403

                
404
        return FALSE;
405
    }
406

                
407
    return TRUE;
408
}
409

                
410
static gboolean
411
load_plugin_functions(NGPlugin *plugin_data)
412
{
413
    GList *list;
414

                
415
    g_return_val_if_fail(function_plugin_data != NULL, FALSE);
416
    g_return_val_if_fail(plugin_data != NULL, FALSE);
417

                
418
    /* Register all the functions */
419
    list = plugin_data->core_data->exported_functions;
420
    while (list) {
421
        NgPluginFunctionOrEventPtr *func_ptr = list->data;
422

                
423
        g_signal_newv(  func_ptr->function_or_event_name,
424
                        NG_TYPE_PLUGIN,
425
                        G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
426
                        0,
427
                        NULL, NULL,
428
                        func_ptr->marshaller,
429
                        func_ptr->return_type,
430
                        func_ptr->num_params,
431
                        (func_ptr->num_params > 0) ? func_ptr->params : NULL);
432

                
433
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_INFO, __FILE__ ":%i Registered implementation for plugin function '%s'", __LINE__, func_ptr->function_or_event_name);
434
        g_signal_connect_swapped(function_plugin_data, func_ptr->function_or_event_name, G_CALLBACK(func_ptr->impl), plugin_data);
435

                
436
        list = g_list_next(list);
437
    }
438

                
439
    return TRUE;
440
}
441

                
442
static gboolean
443
load_plugin_events(NGPlugin *plugin_data)
444
{
445
    GList *list;
446
    int num_params;
447

                
448
    g_return_val_if_fail(plugin_data != NULL, FALSE);
449

                
450
    /* Register all the events */
451
    list = plugin_data->core_data->exported_events;
452
    while (list) {
453
        NgPluginFunctionOrEventPtr *func_ptr = list->data;
454

                
455
        if (func_ptr->is_plugin_event) {
456
            num_params = 2;
457
        } else {
458
            num_params = func_ptr->num_params;
459
        }
460

                
461
        g_signal_newv(  func_ptr->function_or_event_name,
462
                        NG_TYPE_PLUGIN,
463
                        G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
464
                        0,
465
                        NULL, NULL,
466
                        func_ptr->marshaller,
467
                        func_ptr->return_type,
468
                        num_params,
469
                        (num_params > 0) ? func_ptr->params : NULL);
470

                
471
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, __FILE__ ":%i Registered event for plugin function '%s'", __LINE__, func_ptr->function_or_event_name);
472

                
473
        list = g_list_next(list);
474
    }
475

                
476
    return TRUE;
477
}
478

                
479
NGPlugin *
480
get_plugin_by_name(const char *name)
481
{
482
    GList *list = all_available_plugins;
483

                
484
    while (list) {
485
        NGPlugin *plugin_data = list->data;
486

                
487
        if (!strcmp(plugin_data->core_data->name, name)) {
488
            return plugin_data;
489
        }
490

                
491
        list = g_list_next(list);
492
    }
493

                
494
    /* No plugin found */
495

                
496
    return NULL;
497
}
498

                
499
static gboolean
500
unload_plugin(NGPlugin *plugin_data, gboolean forced, char **errmsg)
501
{
502
    g_return_val_if_fail(plugin_data != NULL, FALSE);
503

                
504
    /* FIXME: Perform a dependency check */
505

                
506
    if (!plugin_data->core_data->is_loaded) {
507
        /* Plugin wasn't loaded. Assume a success */
508
        return TRUE;
509
    }
510

                
511
    if (plugin_data->core_data->can_unload_func && !plugin_data->core_data->can_unload_func(plugin_data, errmsg)) {
512
        if (!forced) {
513
            return FALSE;
514
        }
515
    }
516

                
517
    if (plugin_data->core_data->unload_func) {
518
        plugin_data->core_data->unload_func(plugin_data);
519
    }
520
    plugin_data->core_data->is_loaded = FALSE;
521

                
522
    /* FIXME: Implement proper un-registering of functions and events */
523

                
524
    nntpgrab_core_emit_plugin_unloaded(FALSE, plugin_data->core_data->name);
525

                
526
    return TRUE;
527
}
528

                
529
static void
530
destroy_plugin(NGPlugin *plugin_data)
531
{
532
    GList *list;
533
    char *errmsg = NULL;
534

                
535
    g_return_if_fail(plugin_data != NULL);
536

                
537
    ng_plugin_emit_log_msg(plugin_data, NG_LOG_LEVEL_INFO, __FILE__ ":%i Request received to unload plugin '%s'", __LINE__, plugin_data->core_data->name);
538
    if (!unload_plugin(plugin_data, TRUE, &errmsg)) {
539
        ng_plugin_emit_log_msg(plugin_data, NG_LOG_LEVEL_INFO, __FILE__ ":%i Unable to unload plugin '%s' due to an error: %s - Forcing unload anyway", __LINE__, plugin_data->core_data->name, errmsg);
540
        g_free(errmsg);
541
    }
542

                
543
    if (plugin_data->core_data->destroy_func) {
544
        plugin_data->core_data->destroy_func(plugin_data);
545
    }
546

                
547
    all_available_plugins = g_list_remove(all_available_plugins, plugin_data);
548

                
549
    list = all_connected_events;
550
    while (list) {
551
        struct _connected_event *connection = list->data;
552

                
553
        if (connection->plugin_data == plugin_data) {
554
            g_slice_free(struct _connected_event, connection);
555
            all_connected_events = g_list_remove(all_connected_events, connection);
556
            list = all_connected_events;
557
            continue;
558
        }
559

                
560
        list = g_list_next(list);
561
    }
562

                
563
    ng_plugin_emit_log_msg(plugin_data, NG_LOG_LEVEL_INFO, __FILE__ ":%i Plugin '%s' was successfully unloaded", __LINE__, plugin_data->core_data->name);
564

                
565
    g_object_unref(plugin_data);
566
}
567

                
568
void
569
destroy_plugin_by_name(const char *name)
570
{
571
    GList *list = all_available_plugins;
572

                
573
    while (list) {
574
        NGPlugin *plugin_data = list->data;
575

                
576
        if (!strcmp(plugin_data->core_data->name, name)) {
577
            destroy_plugin(plugin_data);
578
            return;
579
        }
580

                
581
        list = g_list_next(list);
582
    }
583

                
584
    /* No plugin found */
585
}
586

                
587
void
588
plugins_initialize(void)
589
{
590
    g_return_if_fail(function_plugin_data == NULL);
591
    g_return_if_fail(all_available_plugins == NULL);
592
    g_return_if_fail(all_plugin_events == NULL);
593

                
594
    function_plugin_data = g_object_new(NG_TYPE_PLUGIN, NULL);
595
    all_plugin_events = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
596

                
597
    init_internal_plugin();
598
}
599

                
600
gboolean
601
plugins_populate_list(char **errmsg)
602
{
603
    const char *filename;
604
    const char *path;
605
    GDir *dir;
606
    GError *err = NULL;
607

                
608
    g_return_val_if_fail(function_plugin_data != NULL, FALSE);
609
    g_return_val_if_fail(all_available_plugins != NULL, FALSE);
610
    g_return_val_if_fail(all_plugin_events != NULL, FALSE);
611

                
612
    if (g_getenv("NNTPGRAB_LIBDIR")) {
613
        path = g_getenv("NNTPGRAB_LIBDIR");
614
    } else {
615
        path = PLUGIN_DIR;
616
    }
617

                
618
    dir = g_dir_open(path, 0, &err);
619

                
620
    if (!dir) {
621
        if (errmsg) {
622
            *errmsg = g_strdup_printf(_("Unable to open plugin directory: %s"), err->message);
623
        }
624
        return FALSE;
625
    }
626

                
627
    while ((filename = g_dir_read_name(dir))) {
628
        try_to_init_plugin(path, filename);
629
    }
630

                
631
    g_dir_close(dir);
632

                
633
    return TRUE;
634
}
635

                
636
static const char *
637
ng_plugins_find_plugin_function_or_event(const char *function_or_event_name)
638
{
639
    GList *list_plugins = all_available_plugins;
640

                
641
    while (list_plugins) {
642
        NGPlugin *plugin_data = list_plugins->data;
643
        GList *list_functions = plugin_data->core_data->exported_functions;
644
        GList *list_events = plugin_data->core_data->exported_events;
645

                
646
        while (list_functions) {
647
            NgPluginFunctionOrEventPtr *func_ptr = list_functions->data;
648

                
649
            if (!strcmp(function_or_event_name, func_ptr->function_or_event_name)) {
650
                return plugin_data->core_data->name;
651
            }
652

                
653
            list_functions = g_list_next(list_functions);
654
        }
655

                
656
        while (list_events) {
657
            NgPluginFunctionOrEventPtr *func_ptr = list_events->data;
658

                
659
            if (!strcmp(function_or_event_name, func_ptr->function_or_event_name)) {
660
                return plugin_data->core_data->name;
661
            }
662

                
663
            list_events = g_list_next(list_events);
664
        }
665

                
666
        list_plugins = g_list_next(list_plugins);
667
    }
668

                
669
    /* No plugin found providing the given function */
670
    return NULL;
671
}
672

                
673
static void
674
ng_plugins_add_dependency(NGPluginCoreData *core_data, const char *plugin_name)
675
{
676
    GList *list = core_data->dependencies;
677

                
678
    /* Is this dependency already registered? */
679
    while (list) {
680
        char *name = list->data;
681

                
682
        if (!strcmp(name, plugin_name)) {
683
            /* Yes it is, bail out */
684
            return;
685
        }
686

                
687
        list = g_list_next(list);
688
    }
689

                
690
    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "Added dependency on plugin '%s' to plugin '%s'", plugin_name, core_data->name);
691

                
692
    /* Dependency not known yet, add it to the list */
693
    core_data->dependencies = g_list_append(core_data->dependencies, (char*) plugin_name);
694
}
695

                
696
static gboolean
697
test_all_plugin_deps_found(GList *ordered_list, NGPlugin *plugin_data)
698
{
699
    GList *list_deps = plugin_data->core_data->dependencies;
700

                
701
    while (list_deps) {
702
        const char *plugin_name = list_deps->data;
703
        GList *list_loaded = ordered_list;
704
        gboolean dep_found = FALSE;
705

                
706
        while (list_loaded) {
707
            NGPlugin *plugin_data_loaded = list_loaded->data;
708

                
709
            if (!strcmp(plugin_data_loaded->core_data->name, plugin_name)) {
710
                /* Dependency of this plugin was already processed */
711
                dep_found = TRUE;
712
                break;
713
            }
714

                
715
            list_loaded = g_list_next(list_loaded);
716
        }
717

                
718
        if (!dep_found) {
719
            return FALSE;
720
        }
721

                
722
        list_deps = g_list_next(list_deps);
723
    }
724

                
725
    /* All dependencies are already resolved */
726
    return TRUE;
727
}
728

                
729
static gboolean
730
test_if_plugin_is_needed_by_another_plugin(NGPlugin *plugin_to_test)
731
{
732
    GList *list = all_available_plugins;
733

                
734
    g_return_val_if_fail(plugin_to_test != NULL, FALSE);
735

                
736
    /* Ignore the 'Internal' plugin as it always needs to be loaded */
737
    if (!strcmp(plugin_to_test->core_data->name, "Internal")) {
738
        return TRUE;
739
    }
740

                
741
    /* Traverse the list with plugins in order to find out if the 'plugin_to_test' isn't required by anything else */
742
    while (list) {
743
        NGPlugin *plugin_data = (NGPlugin*) list->data;
744
        GList *list_deps = plugin_data->core_data->dependencies;
745

                
746
        while (list_deps) {
747
            const char *plugin_name = (const char*) list_deps->data;
748

                
749
            if (!strcmp(plugin_name, plugin_to_test->core_data->name)) {
750
                ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "Plugin '%s' is required by plugin '%s'", plugin_to_test->core_data->name, plugin_data->core_data->name);
751
                plugin_to_test->core_data->is_required = TRUE;
752
                return TRUE;
753
            }
754

                
755
            list_deps = g_list_next(list_deps);
756
        }
757

                
758
        list = g_list_next(list);
759
    }
760

                
761
    plugin_to_test->core_data->is_required = FALSE;
762
    return FALSE;
763
}
764

                
765
static int
766
calculate_num_required_plugins(void)
767
{
768
    GList *list = all_available_plugins;
769
    int count = 0;
770

                
771
    while (list) {
772
        NGPlugin *plugin_data = list->data;
773

                
774
        if (plugin_data->core_data->is_required) {
775
            count++;
776
        }
777

                
778
        list = g_list_next(list);
779
    }
780

                
781
    return count;
782
}
783

                
784
static void
785
log_dependency_tree(GList *ordered_list)
786
{
787
    char list_str[1024];
788
    GList *list;
789

                
790
    memset(&list_str, 0, sizeof(list_str));
791
    list = ordered_list;
792
    while (list) {
793
        NGPlugin *plugin_data = list->data;
794

                
795
        if (strlen(list_str) > 0) {
796
            strncat(list_str, ", ", sizeof(list_str) - strlen(list_str) - 1);
797
            strncat(list_str, plugin_data->core_data->name, sizeof(list_str) - strlen(list_str) - 1);
798
        } else {
799
            strncpy(list_str, plugin_data->core_data->name, sizeof(list_str));
800
        }
801

                
802
        list = g_list_next(list);
803
    }
804
    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "Ordered list = %s", list_str);
805

                
806
    memset(&list_str, 0, sizeof(list_str));
807
    list = all_available_plugins;
808
    while (list) {
809
        NGPlugin *plugin_data = list->data;
810

                
811
        if (strlen(list_str) > 0) {
812
            strncat(list_str, ", ", sizeof(list_str) - strlen(list_str) - 1);
813
            strncat(list_str, plugin_data->core_data->name, sizeof(list_str) - strlen(list_str) - 1);
814
        } else {
815
            strncpy(list_str, plugin_data->core_data->name, sizeof(list_str));
816
        }
817

                
818
        list = g_list_next(list);
819
    }
820
    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "All available plugins = %s", list_str);
821
}
822

                
823
static GList *
824
plugins_generate_dependency_tree(gboolean only_load_required_plugins, char **errmsg)
825
{
826
    GList *ordered_list = NULL;
827
    GList *list_plugins = all_available_plugins;
828
    int expected_length;
829
    int num_iterations = 0;
830

                
831
    /* Find out which plugins depend on each other */
832
    while (list_plugins) {
833
        NGPlugin *plugin_data = list_plugins->data;
834

                
835
        /* Find out which plugins offer the functions which this plugin requires */
836
        GList *list_funcs_and_events_required = plugin_data->core_data->required_functions_and_events;
837
        while (list_funcs_and_events_required) {
838
            char *function_or_event_name = list_funcs_and_events_required->data;
839
            const char *plugin_name;
840

                
841
            plugin_name = ng_plugins_find_plugin_function_or_event(function_or_event_name);
842
            if (!plugin_name) {
843
                plugin_data->core_data->initialisation_error = TRUE;
844
                snprintf(plugin_data->core_data->initialisation_errmsg, sizeof(plugin_data->core_data->initialisation_errmsg) - 1, _("Unable to find a plugin which offers the function or event '%s' (required by the plugin '%s')"), function_or_event_name, plugin_data->core_data->name);
845
                ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_FATAL, plugin_data->core_data->initialisation_errmsg);
846
                break;
847
            }
848

                
849
            ng_plugins_add_dependency(plugin_data->core_data, plugin_name);
850

                
851
            list_funcs_and_events_required = g_list_next(list_funcs_and_events_required);
852
        }
853

                
854
        list_plugins = g_list_next(list_plugins);
855
    }
856

                
857
    /* Find out the order in which we need to load plugins */
858
    expected_length = g_list_length(all_available_plugins);
859
    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "Expected length of ordered list initially set to %i", expected_length);
860
    while (g_list_length(ordered_list) != expected_length) {
861
        /* Keep on traversing the list of plugins until the order is fully known */
862
        list_plugins = all_available_plugins;
863
        gboolean dep_added = FALSE;
864

                
865
        num_iterations++;
866
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "Iteration %i in dependency resolving", num_iterations);
867

                
868
        while (list_plugins) {
869
            NGPlugin *plugin_data = list_plugins->data;
870

                
871
            if (!plugin_data->core_data->initialisation_error &&
872
                !g_list_find(ordered_list, plugin_data) &&
873
                test_all_plugin_deps_found(ordered_list, plugin_data)) {
874

                
875
                if (only_load_required_plugins && !test_if_plugin_is_needed_by_another_plugin(plugin_data)) {
876
                    list_plugins = g_list_next(list_plugins);
877
                    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "Marked plugin '%s' as optional plugin", plugin_data->core_data->name);
878

                
879
                    expected_length = calculate_num_required_plugins();
880
                    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "Expected length of ordered list updated to %i", expected_length);
881

                
882
                    continue;
883
                }
884

                
885
                ordered_list = g_list_append(ordered_list, plugin_data);
886
                dep_added = TRUE;
887
                ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "Added plugin '%s' to the ordered_list", plugin_data->core_data->name);
888
            }
889

                
890
            list_plugins = g_list_next(list_plugins);
891
        }
892

                
893
        if (!dep_added && g_list_length(ordered_list) != expected_length) {
894
            /* Endless loop detected! */
895
            if (errmsg) {
896
                *errmsg = g_strdup_printf(__FILE__ ":%i Unable to resolve dependency order", __LINE__);
897
            }
898

                
899
            ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_FATAL, "Unable to resolve dependency order");
900

                
901
            log_dependency_tree(ordered_list);
902

                
903
            return NULL;
904
        }
905
    }
906

                
907
    log_dependency_tree(ordered_list);
908

                
909
    return ordered_list;
910
}
911

                
912
gboolean
913
plugins_load_plugin(NGPlugin *plugin_data, char **errmsg)
914
{
915
    g_return_val_if_fail(plugin_data != NULL, FALSE);
916

                
917
    ng_plugin_emit_log_msg(plugin_data, NG_LOG_LEVEL_INFO, __FILE__ ":%i Request received to load plugin '%s'", __LINE__, plugin_data->core_data->name);
918

                
919
    /* TODO: Test if all dependencies are already loaded */
920

                
921
    if (plugin_data->core_data->is_loaded) {
922
        if (errmsg) {
923
            *errmsg = g_strdup_printf(_("Plugin is already loaded"));
924
        }
925
        return FALSE;
926
    }
927

                
928
    if (!load_plugin_base(plugin_data, errmsg) ||
929
        !load_plugin_functions(plugin_data) ||
930
        !load_plugin_events(plugin_data)) {
931

                
932
        ng_plugin_emit_log_msg(plugin_data, NG_LOG_LEVEL_INFO, __FILE__ ":%i Loading of plugin '%s' FAILED: %s", __LINE__, plugin_data->core_data->name, *errmsg);
933

                
934
        return FALSE;
935
    } else {
936
        plugin_data->core_data->is_loaded = TRUE;
937
        nntpgrab_core_emit_plugin_loaded(FALSE, plugin_data->core_data->name, TRUE);
938

                
939
        ng_plugin_emit_log_msg(plugin_data, NG_LOG_LEVEL_INFO, __FILE__ ":%i Plugin '%s' successfully loaded", __LINE__, plugin_data->core_data->name);
940

                
941
        return TRUE;
942
    }
943
}
944

                
945
gboolean
946
plugins_load(NntpgrabCore *core, Configuration *configuration, char **errmsg)
947
{
948
    GList *list = NULL;
949
    GList *ordered_list = NULL;
950

                
951
    g_return_val_if_fail(configuration != NULL, FALSE);
952
    config = configuration;
953
    g_signal_connect(config, "config_changed", G_CALLBACK(config_changed), NULL);
954
    g_signal_connect(core, "plugin_loaded", G_CALLBACK(plugin_loaded), NULL);
955
    config_changed(config, NULL);
956

                
957
    list = ordered_list = plugins_generate_dependency_tree(TRUE, errmsg);
958
    if (!ordered_list) {
959
        return FALSE;
960
    }
961

                
962
    while (list) {
963
        NGPlugin *plugin_data = list->data;
964

                
965
        if (!plugins_load_plugin(plugin_data, errmsg)) {
966
            g_list_free(ordered_list);
967
            return FALSE;
968
        }
969

                
970
        list = g_list_next(list);
971
    }
972

                
973
    g_list_free(ordered_list);
974

                
975
    /* As we have a circular dependency between the Internal plugin and the PAR2 plugin we need to create the link manually here */
976
    ng_plugin_connect_event(NULL, "par2_repair_failure", NG_PLUGIN_FUNCTION(download_queue_on_par2_repair_failure), NULL);
977

                
978
    return TRUE;
979
}
980

                
981
void
982
plugins_destroy(void)
983
{
984
    GList *list = NULL;
985
    GList *ordered_list = NULL;
986

                
987
    g_return_if_fail(function_plugin_data != NULL);
988
    g_return_if_fail(all_plugin_events != NULL);
989

                
990
    /* Perform a cleanup in reverse order */
991
    list = ordered_list = g_list_reverse(plugins_generate_dependency_tree(FALSE, NULL));
992
    if (!ordered_list) {
993
        return;
994
    }
995

                
996
    while (list) {
997
        NGPlugin *plugin_data = list->data;
998

                
999
        destroy_plugin(plugin_data);
1000

                
1001
        list = g_list_next(list);
1002
    }
1003

                
1004
    g_list_free(ordered_list);
1005
    ordered_list = NULL;
1006

                
1007
    g_object_unref(function_plugin_data);
1008
    function_plugin_data = NULL;
1009

                
1010
    g_hash_table_destroy(all_plugin_events);
1011
    all_plugin_events = NULL;
1012

                
1013
    g_list_free(all_available_plugins);
1014
    all_available_plugins = NULL;
1015
}
1016

                
1017
ngboolean
1018
ng_plugin_get_is_loaded(NGPlugin *plugin_data)
1019
{
1020
    g_return_val_if_fail(plugin_data != NULL, FALSE);
1021

                
1022
    if (plugin_data->core_data && plugin_data->core_data->is_loaded) {
1023
        return TRUE;
1024
    } else {
1025
        return FALSE;
1026
    }
1027
}
1028

                
1029
void ng_plugin_set_name(NGPlugin *plugin_data, const char *name)
1030
{
1031
    g_return_if_fail(plugin_data != NULL);
1032
    g_return_if_fail(name != NULL);
1033

                
1034
    strncpy(plugin_data->core_data->name, name, sizeof(plugin_data->core_data->name) - 1);
1035
}
1036

                
1037
void ng_plugin_set_version(NGPlugin *plugin_data, const char *version)
1038
{
1039
    g_return_if_fail(plugin_data != NULL);
1040
    g_return_if_fail(version != NULL);
1041

                
1042
    strncpy(plugin_data->core_data->version, version, sizeof(plugin_data->core_data->version) - 1);
1043
}
1044

                
1045
void ng_plugin_set_author(NGPlugin *plugin_data, const char *author)
1046
{
1047
    g_return_if_fail(plugin_data != NULL);
1048
    g_return_if_fail(author != NULL);
1049

                
1050
    strncpy(plugin_data->core_data->author, author, sizeof(plugin_data->core_data->author) - 1);
1051
}
1052

                
1053
void ng_plugin_set_url(NGPlugin *plugin_data, const char *url)
1054
{
1055
    g_return_if_fail(plugin_data != NULL);
1056
    g_return_if_fail(url != NULL);
1057

                
1058
    strncpy(plugin_data->core_data->url, url, sizeof(plugin_data->core_data->url) - 1);
1059
}
1060

                
1061
void ng_plugin_set_description(NGPlugin *plugin_data, const char *description)
1062
{
1063
    g_return_if_fail(plugin_data != NULL);
1064
    g_return_if_fail(description != NULL);
1065

                
1066
    strncpy(plugin_data->core_data->description, description, sizeof(plugin_data->core_data->description) - 1);
1067
}
1068

                
1069
static ngboolean
1070
ng_plugin_register_function_or_event_va(GList **exported_list, NGPlugin *plugin_data, const char *function_or_event_name, gboolean is_plugin_event, NGPluginFunction impl, GSignalCMarshaller marshaller, GType return_type, int num_params, va_list args)
1071
{
1072
    NgPluginFunctionOrEventPtr *func_ptr;
1073
    int i;
1074
    int num_params_real;
1075

                
1076
    g_return_val_if_fail(exported_list != NULL, FALSE);
1077
    g_return_val_if_fail(plugin_data != NULL, FALSE);
1078
    g_return_val_if_fail(plugin_data->core_data != NULL, FALSE);
1079
    g_return_val_if_fail(function_or_event_name != NULL, FALSE);
1080
    g_return_val_if_fail(num_params >= 0, FALSE);
1081

                
1082
    if (is_plugin_event) {
1083
        num_params_real = 2;
1084
    } else {
1085
        num_params_real = num_params;
1086
    }
1087

                
1088
    func_ptr = g_slice_new0(NgPluginFunctionOrEventPtr);
1089
    strncpy(func_ptr->function_or_event_name, function_or_event_name, sizeof(func_ptr->function_or_event_name) - 1);
1090
    func_ptr->impl = impl;
1091
    func_ptr->marshaller = marshaller;
1092
    func_ptr->return_type = return_type;
1093
    func_ptr->num_params = num_params;
1094
    func_ptr->params = g_slice_alloc0(sizeof(GType) * num_params_real);
1095
    func_ptr->is_plugin_event = is_plugin_event;
1096

                
1097
    for (i = 0; i < num_params_real; i++) {
1098
        func_ptr->params[i] = va_arg(args, GType);
1099
    }
1100

                
1101
    *exported_list = g_list_append(*exported_list, func_ptr);
1102

                
1103
    return TRUE;
1104
}
1105

                
1106
ngboolean ng_plugin_register_internal_function(const char *function_name, NGPluginFunction impl, GSignalCMarshaller marshaller, GType return_type, int num_params, ...)
1107
{
1108
    GList *list = all_available_plugins;
1109
    while (list) {
1110
        NGPlugin *plugin_data = list->data;
1111

                
1112
        if (!strcmp(plugin_data->core_data->name, "Internal")) {
1113
            char function_name_new[128];
1114
            ngboolean ret;
1115
            va_list ap;
1116

                
1117
            memset(function_name_new, 0, sizeof(function_name_new));
1118
            strcpy(function_name_new, "function_");
1119
            strncat(function_name_new, function_name, sizeof(function_name_new) - strlen(function_name_new) - 1);
1120

                
1121
            va_start(ap, num_params);
1122
            ret = ng_plugin_register_function_or_event_va(&plugin_data->core_data->exported_functions, plugin_data, function_name_new, FALSE, impl, marshaller, return_type, num_params, ap);
1123
            va_end(ap);
1124

                
1125
            return ret;
1126
        }
1127

                
1128
        list = g_list_next(list);
1129
    }
1130

                
1131
    /* Internal plugin not found */
1132
    return FALSE;
1133
}
1134

                
1135
ngboolean ng_plugin_register_function(NGPlugin *plugin_data, const char *function_name, NGPluginFunction impl, GSignalCMarshaller marshaller, GType return_type, int num_params, ...)
1136
{
1137
    ngboolean ret;
1138
    va_list ap;
1139
    char function_name_new[128];
1140

                
1141
    memset(function_name_new, 0, sizeof(function_name_new));
1142
    strcpy(function_name_new, "function_");
1143
    strncat(function_name_new, function_name, sizeof(function_name_new) - strlen(function_name_new) - 1);
1144

                
1145
    va_start(ap, num_params);
1146
    ret = ng_plugin_register_function_or_event_va(&plugin_data->core_data->exported_functions, function_plugin_data, function_name_new, FALSE, impl, marshaller, return_type, num_params, ap);
1147
    va_end(ap);
1148

                
1149
    return ret;
1150
}
1151

                
1152
static void
1153
ng_plugin_set_required_function_or_event(NGPlugin *plugin_data, const char *function_or_event_name)
1154
{
1155
    GList *list;
1156

                
1157
    g_return_if_fail(plugin_data != NULL);
1158
    g_return_if_fail(plugin_data->core_data != NULL);
1159
    g_return_if_fail(function_or_event_name != NULL);
1160

                
1161
    /* Is this function already marked as required? */
1162
    list = plugin_data->core_data->required_functions_and_events;
1163
    while (list) {
1164
        const char *name = list->data;
1165

                
1166
        if (!strcmp(function_or_event_name, name)) {
1167
            ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_INFO, __FILE__ ":%i unable to mark function of event '%s' for plugin '%s' as required. It's already marked as required", __LINE__, function_or_event_name, plugin_data->core_data->name);
1168
            return;
1169
        }
1170

                
1171
        list = g_list_next(list);
1172
    }
1173

                
1174
    /* Add it to the list */
1175
    plugin_data->core_data->required_functions_and_events = g_list_append(plugin_data->core_data->required_functions_and_events, g_strdup(function_or_event_name));
1176
}
1177

                
1178
void
1179
ng_plugin_set_required_function(NGPlugin *plugin_data, const char *function_name)
1180
{
1181
    char function_name_new[128];
1182

                
1183
    memset(function_name_new, 0, sizeof(function_name_new));
1184
    strcpy(function_name_new, "function_");
1185
    strncat(function_name_new, function_name, sizeof(function_name_new) - strlen(function_name_new) - 1);
1186

                
1187
    ng_plugin_set_required_function_or_event(plugin_data, function_name_new);
1188
}
1189

                
1190
static ngboolean
1191
ng_plugin_call_function_or_event_va(NGPlugin *function_or_event, const char *function_or_event_name, va_list ap)
1192
{
1193
    GQuark detail = 0;
1194
    guint signal_id;
1195

                
1196
    g_return_val_if_fail(function_or_event != NULL, FALSE);
1197
    g_return_val_if_fail(function_or_event->core_data != NULL, FALSE);
1198
    g_return_val_if_fail(function_or_event_name != NULL, FALSE);
1199

                
1200
    if (!g_signal_parse_name(function_or_event_name, NG_TYPE_PLUGIN, &signal_id, &detail, TRUE)) {
1201
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_INFO, __FILE__ ":%i No plugin implementation found for function or event '%s'", __LINE__, function_or_event_name);
1202
        return FALSE;
1203
    }
1204

                
1205
    g_signal_emit_valist(function_or_event, signal_id, detail, ap);
1206

                
1207
    return TRUE;
1208

                
1209
}
1210

                
1211
ngboolean
1212
ng_plugin_call(NGPlugin *plugin_data, const char *function_name, ...)
1213
{
1214
    ngboolean ret;
1215
    va_list ap;
1216
    char function_name_new[128];
1217

                
1218
    g_return_val_if_fail(function_plugin_data != NULL, FALSE);
1219
    g_return_val_if_fail(function_plugin_data->core_data != NULL, FALSE);
1220
    g_return_val_if_fail(function_name != NULL, FALSE);
1221

                
1222
    memset(function_name_new, 0, sizeof(function_name_new));
1223
    strcpy(function_name_new, "function_");
1224
    strncat(function_name_new, function_name, sizeof(function_name_new) - strlen(function_name_new) - 1);
1225

                
1226
    va_start(ap, function_name);
1227
    ret = ng_plugin_call_function_or_event_va(function_plugin_data, function_name_new, ap);
1228
    va_end(ap);
1229

                
1230
    return ret;
1231
}
1232

                
1233
static ngboolean
1234
ng_plugin_create_event_va(NGPlugin *plugin_data, const char *event_name_new, int num_params, ...)
1235
{
1236
    ngboolean ret;
1237
    va_list ap;
1238

                
1239
    va_start(ap, num_params);
1240
    ret = ng_plugin_register_function_or_event_va(&plugin_data->core_data->exported_events, plugin_data, event_name_new, TRUE, NULL, nntpgrab_marshal_VOID__STRING_BOXED, G_TYPE_NONE, num_params, ap);
1241
    va_end(ap);
1242

                
1243
    return ret;
1244
}
1245

                
1246
ngboolean
1247
ng_plugin_create_event(NGPlugin *plugin_data, const char *event_name, int num_params)
1248
{
1249
    ngboolean ret;
1250
    char event_name_new[128];
1251

                
1252
    memset(event_name_new, 0, sizeof(event_name_new));
1253
    strcpy(event_name_new, "event_");
1254
    strncat(event_name_new, event_name, sizeof(event_name_new) - strlen(event_name_new) - 1);
1255

                
1256
    ret = ng_plugin_create_event_va(plugin_data, event_name_new, 2, G_TYPE_STRING, G_TYPE_STRV);
1257

                
1258
    if (ret) {
1259
        /* Add the event_name and plugin data to a hashtable so that it can be used to emit events on the right objects */
1260
        g_hash_table_insert(all_plugin_events, g_strdup(event_name), plugin_data);
1261
    }
1262

                
1263
    return ret;
1264
}
1265

                
1266
ngboolean
1267
ng_plugin_create_internal_event(const char *event_name, GSignalCMarshaller marshaller, GType return_type, int num_params, ...)
1268
{
1269
    char event_name_new[128];
1270
    ngboolean ret;
1271
    va_list ap;
1272
    NGPlugin *plugin_data;
1273

                
1274
    plugin_data = get_plugin_by_name("Internal");
1275
    if (!plugin_data) {
1276
        return FALSE;
1277
    }
1278

                
1279
    memset(event_name_new, 0, sizeof(event_name_new));
1280
    strcpy(event_name_new, "event_");
1281
    strncat(event_name_new, event_name, sizeof(event_name_new) - strlen(event_name_new) - 1);
1282

                
1283
    va_start(ap, num_params);
1284
    ret = ng_plugin_register_function_or_event_va(&plugin_data->core_data->exported_events, plugin_data, event_name_new, FALSE, NULL, marshaller, return_type, num_params, ap);
1285
    va_end(ap);
1286

                
1287
    if (ret) {
1288
        /* Add the event_name and plugin data to a hashtable so that it can be used to emit events on the right objects */
1289
        g_hash_table_insert(all_plugin_events, g_strdup(event_name), plugin_data);
1290
    }
1291

                
1292
    return ret;
1293
}
1294

                
1295
void
1296
ng_plugin_set_required_event(NGPlugin *plugin_data, const char *event_name)
1297
{
1298
    char event_name_new[128];
1299

                
1300
    memset(event_name_new, 0, sizeof(event_name_new));
1301
    strcpy(event_name_new, "event_");
1302
    strncat(event_name_new, event_name, sizeof(event_name_new) - strlen(event_name_new) - 1);
1303

                
1304
    ng_plugin_set_required_function_or_event(plugin_data, event_name_new);
1305
}
1306

                
1307
ngboolean
1308
ng_plugin_connect_event(NGPlugin *plugin_data, const char *event_name, NGPluginFunction impl, void *data)
1309
{
1310
    char event_name_new[128];
1311
    NGPlugin *event_plugin;
1312
    struct _connected_event *connection;
1313

                
1314
    g_return_val_if_fail(event_name != NULL, FALSE);
1315
    g_return_val_if_fail(impl != NULL, FALSE);
1316

                
1317
    if (!plugin_data) {
1318
        plugin_data = get_plugin_by_name("Internal");
1319
        if (!plugin_data) {
1320
            return FALSE;
1321
        }
1322
    }
1323

                
1324
    event_plugin = g_hash_table_lookup(all_plugin_events, event_name);
1325
    if (!event_plugin) {
1326
        return FALSE;
1327
    }
1328

                
1329
    memset(event_name_new, 0, sizeof(event_name_new));
1330
    strcpy(event_name_new, "event_");
1331
    strncat(event_name_new, event_name, sizeof(event_name_new) - strlen(event_name_new) - 1);
1332

                
1333
    g_signal_connect(plugin_data, event_name_new, G_CALLBACK(impl), data);
1334
    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, __FILE__ ":%i event handler for event '%s' registered to callback at address %p", __LINE__, event_name, impl);
1335

                
1336
    /* Keep track of all the connected events so it can be used to emit events more quickly */
1337
    connection = g_slice_new0(struct _connected_event);
1338
    connection->plugin_data = plugin_data;
1339
    strncpy(connection->event_name, event_name, sizeof(connection->event_name) - 1);
1340
    connection->event_name_hash = g_str_hash(event_name);
1341
    connection->impl = impl;
1342
    all_connected_events = g_list_append(all_connected_events, connection);
1343

                
1344
    return TRUE;
1345
}
1346

                
1347
ngboolean
1348
ng_plugin_disconnect_event_by_func(NGPlugin *plugin_data, NGPluginFunction impl, void *data)
1349
{
1350
    GList *list;
1351

                
1352
    g_return_val_if_fail(impl != NULL, FALSE);
1353

                
1354
    list = all_connected_events;
1355
    while (list) {
1356
        struct _connected_event *connection = list->data;
1357

                
1358
        if (connection->plugin_data == plugin_data && connection->impl == impl) {
1359
            g_slice_free(struct _connected_event, connection);
1360
            all_connected_events = g_list_remove(all_connected_events, connection);
1361

                
1362
            g_signal_handlers_disconnect_by_func(plugin_data, G_CALLBACK(impl), data);
1363

                
1364
            return TRUE;
1365
        }
1366

                
1367
        list = g_list_next(list);
1368
    }
1369

                
1370
    return FALSE;
1371
}
1372

                
1373
ngboolean
1374
ng_plugin_emit_internal_event(const char *event_name, ...)
1375
{
1376
    char event_name_new[128];
1377
    guint event_name_hash;
1378
    ngboolean ret = TRUE;
1379
    va_list ap;
1380
    GList *list;
1381

                
1382
    g_return_val_if_fail(event_name != NULL, FALSE);
1383

                
1384
    event_name_hash = g_str_hash(event_name);
1385

                
1386
    memset(event_name_new, 0, sizeof(event_name_new));
1387
    strcpy(event_name_new, "event_");
1388
    strncat(event_name_new, event_name, sizeof(event_name_new) - strlen(event_name_new) - 1);
1389

                
1390
    list = all_connected_events;
1391
    while (list) {
1392
        struct _connected_event *connection = list->data;
1393

                
1394
        if (connection->event_name_hash == event_name_hash &&
1395
            !strcmp(connection->event_name, event_name)) {
1396

                
1397
            va_start(ap, event_name);
1398
            ret = ng_plugin_call_function_or_event_va(connection->plugin_data, event_name_new, ap);
1399
            va_end(ap);
1400

                
1401
#if 0 
1402
            if (!ret) {
1403
                break;
1404
            }
1405
#endif
1406
        }
1407

                
1408
        list = g_list_next(list);
1409
    }
1410

                
1411
    return ret;
1412
}
1413

                
1414
ngboolean
1415
ng_plugin_emit_event(NGPlugin *plugin_data, const char *event_name, const char **params)
1416
{
1417
    g_return_val_if_fail(plugin_data != NULL, FALSE);
1418
    g_return_val_if_fail(event_name != NULL, FALSE);
1419

                
1420
    if (!ng_plugin_emit_internal_event(event_name, plugin_data->core_data->name, params)) {
1421
        return FALSE;
1422
    }
1423

                
1424
    nntpgrab_core_emit_plugin_event(FALSE, plugin_data->core_data->name, event_name, params);
1425

                
1426
    return TRUE;
1427
}
1428

                
1429
static gboolean print_logging = FALSE;
1430
static gboolean emit_logging_to_frontend = FALSE;
1431
static gboolean emit_logging_to_plugins = FALSE;
1432
static gboolean enable_logger_plugin = FALSE;
1433
static gboolean logger_plugin_loaded = FALSE;
1434

                
1435
static void
1436
config_changed(Configuration *obj, gpointer data)
1437
{
1438
    NGConfigOpts opts = configuration_get_opts(obj);
1439
    enable_logger_plugin = opts.enable_logger;
1440
}
1441

                
1442
static void
1443
plugin_loaded(NntpgrabCore *core, const char *plugin_name, gboolean is_persistent, gpointer data)
1444
{
1445
    if (!strcmp(plugin_name, "JSON-RPC")) {
1446
        logger_plugin_loaded = TRUE;
1447
    }
1448
}
1449

                
1450
void
1451
plugins_print_logging(void)
1452
{
1453
    print_logging = TRUE;
1454
}
1455

                
1456
void
1457
plugins_set_emit_log_messages_to_frontend(ngboolean val)
1458
{
1459
    emit_logging_to_frontend = val;
1460
}
1461

                
1462
static void
1463
plugins_set_emit_log_messages_to_plugins(ngboolean val)
1464
{
1465
    emit_logging_to_plugins = val;
1466
}
1467

                
1468
void
1469
ng_plugin_emit_log_msg(NGPlugin *plugin_data, NGLogLevel log_level, const char *format, ...)
1470
{
1471
    va_list args;
1472
    const char *component;
1473
    char msg[1024];
1474

                
1475
    g_return_if_fail(log_level != NG_LOG_LEVEL_ALL);
1476
    g_return_if_fail(format != NULL);
1477

                
1478
    if (!emit_logging_to_frontend && !emit_logging_to_plugins && !enable_logger_plugin && !print_logging) {
1479
        return;
1480
    }
1481

                
1482
    if (plugin_data && plugin_data->core_data && plugin_data->core_data->name) {
1483
        component = plugin_data->core_data->name;
1484
    } else {
1485
        component = "NNTPGrab Core";
1486
    }
1487

                
1488
    va_start(args, format);
1489
    memset(msg, 0, sizeof(msg));
1490
    vsnprintf(msg, sizeof(msg) - 1, format, args);
1491
    va_end(args);
1492

                
1493
    if (print_logging) {
1494
        /* This block of code can be used to dump log messages to the console */
1495
        GLogLevelFlags level;
1496

                
1497
        switch (log_level) {
1498
            case NG_LOG_LEVEL_WARNING:
1499
            case NG_LOG_LEVEL_ERROR:
1500
            case NG_LOG_LEVEL_FATAL:
1501
                /* Disabled for now as it causes message to be shown twice in frontends due to the g_set_log_handler call in gui_base */
1502
                //level = G_LOG_LEVEL_WARNING;
1503
                //break;
1504

                
1505
        case NG_LOG_LEVEL_DEBUG:
1506
            case NG_LOG_LEVEL_INFO:
1507
            default:
1508
                level = G_LOG_LEVEL_INFO;
1509
        }
1510

                
1511
        va_start(args, format);
1512
        g_logv(component, level, format, args);
1513
        va_end(args);
1514
    }
1515

                
1516
    if (emit_logging_to_frontend || emit_logging_to_plugins || (enable_logger_plugin && logger_plugin_loaded)) {
1517
        nntpgrab_core_emit_log_message(FALSE, component, log_level, msg, emit_logging_to_frontend, (emit_logging_to_plugins || (enable_logger_plugin && logger_plugin_loaded)));
1518
    }
1519
}
1520

                
1521
NGList *
1522
ng_plugin_get_avail_plugins()
1523
{
1524
    NGList *ret = NULL;
1525
    GList *list = all_available_plugins;
1526

                
1527
    while (list) {
1528
        NGPlugin *plugin_data = list->data;
1529
        NGPluginInfo *plugin_info = g_slice_new0(NGPluginInfo);
1530
        GList *list2;
1531

                
1532
        plugin_info->is_loaded = plugin_data->core_data->is_loaded;
1533
        strncpy(plugin_info->name, plugin_data->core_data->name, sizeof(plugin_data->core_data->name) - 1);
1534
        strncpy(plugin_info->version, plugin_data->core_data->version, sizeof(plugin_data->core_data->version) - 1);
1535
        strncpy(plugin_info->author, plugin_data->core_data->author, sizeof(plugin_data->core_data->author) - 1);
1536
        strncpy(plugin_info->url, plugin_data->core_data->url, sizeof(plugin_data->core_data->url) - 1);
1537
        strncpy(plugin_info->description, plugin_data->core_data->description, sizeof(plugin_data->core_data->description) - 1);
1538

                
1539
        list2 = plugin_data->core_data->dependencies;
1540
        while (list2) {
1541
            plugin_info->dependencies = ng_list_append(plugin_info->dependencies, g_strdup((const char*) list2->data));
1542
            list2 = g_list_next(list2);
1543
        }
1544

                
1545
        list = g_list_next(list);
1546
    }
1547

                
1548
    return ret;
1549
}
1550

                
1551
void
1552
ng_plugin_free_avail_plugins(NGList *list_avail_plugins)
1553
{
1554
    NGList *list = list_avail_plugins;
1555

                
1556
    while (list) {
1557
        NGPluginInfo *plugin_info = list->data;
1558
        NGList *list2;
1559

                
1560
        list2 = plugin_info->dependencies;
1561
        while (list2) {
1562
            g_free(list2->data);
1563
            list2 = ng_list_next(list2);
1564
        }
1565

                
1566
        ng_list_free(plugin_info->dependencies);
1567
        g_slice_free(NGPluginInfo, plugin_info);
1568

                
1569
        list = ng_list_next(list);
1570
    }
1571

                
1572
    ng_list_free(list_avail_plugins);
1573
}
1574

                
1575
/* Exported functions */
1576
NGList *
1577
plugins_get_avail_plugins (void)
1578
{
1579
    GList *list;
1580
    GList *list_ret = NULL;
1581

                
1582
    list = all_available_plugins;
1583
    while (list) {
1584
        NGPlugin *plugin_data = list->data;
1585

                
1586
        /* Don't expose some plugins */
1587
        if (!strcmp(plugin_data->core_data->name, "Internal") ||
1588
            !strcmp(plugin_data->core_data->name, "Decoder") ||
1589
            !strcmp(plugin_data->core_data->name, "PAR2") ||
1590
            !strcmp(plugin_data->core_data->name, "Unpack") ||
1591
            !strcmp(plugin_data->core_data->name, "Auto-import")) {
1592

                
1593
            list = g_list_next(list);
1594
            continue;
1595
        }
1596

                
1597
        list_ret = g_list_append(list_ret, g_strdup(plugin_data->core_data->name));
1598

                
1599
        list = g_list_next(list);
1600
    }
1601

                
1602
    return (NGList*) list_ret;
1603
}
1604

                
1605
void
1606
plugins_free_avail_plugins (NGList *plugins)
1607
{
1608
    NGList *list;
1609

                
1610
    list = plugins;
1611
    while (list) {
1612
        g_free(list->data);
1613
        list = ng_list_next(list);
1614
    }
1615

                
1616
    g_list_free((GList*) plugins);
1617
}
1618

                
1619
ngboolean
1620
plugins_get_plugin_info (const char *plugin_name, NNTPGrabPluginInfo *plugin_info)
1621
{
1622
    GList *list = all_available_plugins;
1623

                
1624
    g_return_val_if_fail(plugin_name != NULL, FALSE);
1625
    g_return_val_if_fail(plugin_info != NULL, FALSE);
1626

                
1627
    while (list) {
1628
        NGPlugin *plugin_data = list->data;
1629

                
1630
        if (!strcmp(plugin_data->core_data->name, plugin_name)) {
1631
            /* Plugin found */
1632
            memset(plugin_info, 0, sizeof(NNTPGrabPluginInfo));
1633

                
1634
            strncpy(plugin_info->name, plugin_data->core_data->name, sizeof(plugin_info->name) - 1);
1635
            strncpy(plugin_info->version, plugin_data->core_data->version, sizeof(plugin_info->version) - 1);
1636
            strncpy(plugin_info->author, plugin_data->core_data->author, sizeof(plugin_info->author) - 1);
1637
            strncpy(plugin_info->url, plugin_data->core_data->url, sizeof(plugin_info->url) - 1);
1638
            strncpy(plugin_info->description, plugin_data->core_data->description, sizeof(plugin_info->description) - 1);
1639

                
1640
            plugin_info->is_loaded = plugin_data->core_data->is_loaded;
1641
            plugin_info->is_persistent = plugin_data->core_data->is_required;
1642

                
1643
            return TRUE;
1644
        }
1645

                
1646
        list = g_list_next(list);
1647
    }
1648

                
1649
    /* Plugin not found */
1650
    return FALSE;
1651
}
1652

                
1653
ngboolean
1654
plugins_load_plugin_by_name (const char *plugin_name, char **errmsg)
1655
{
1656
    NGPlugin *plugin_data = get_plugin_by_name(plugin_name);
1657

                
1658
    g_return_val_if_fail(plugin_name != NULL, FALSE);
1659
    /* NOTE: errmsg MIGHT be NULL */
1660

                
1661
    if (!plugin_data) {
1662
        if (errmsg) {
1663
            *errmsg = g_strdup_printf(_("Unable to find a plugin named '%s'"), plugin_name);
1664
        }
1665

                
1666
        return FALSE;
1667
    }
1668

                
1669
    return plugins_load_plugin(plugin_data, errmsg);
1670
}
1671

                
1672
ngboolean
1673
plugins_unload_plugin_by_name (const char *plugin_name, char **errmsg)
1674
{
1675
    NGPlugin *plugin_data = get_plugin_by_name(plugin_name);
1676

                
1677
    g_return_val_if_fail(plugin_name != NULL, FALSE);
1678
    /* NOTE: errmsg MIGHT be NULL */
1679

                
1680
    if (!plugin_data) {
1681
        if (errmsg) {
1682
            *errmsg = g_strdup_printf(_("Unable to find a plugin named '%s'"), plugin_name);
1683
        }
1684

                
1685
        return FALSE;
1686
    }
1687

                
1688
#if 0 
1689
    return unload_plugin(plugin_data, FALSE, errmsg);
1690
#else
1691
    if (errmsg) {
1692
        *errmsg = g_strdup(_("Unloading plugins isn't implemented yet"));
1693
    }
1694
    return FALSE;
1695
#endif
1696
}
1697

                
1698
ngboolean
1699
plugins_set_persistent (const char *plugin_name, ngboolean persistent)
1700
{
1701
    /* TODO: Needs to be implemented */
1702
    return FALSE;
1703
}