Statistics
| Revision:

root / trunk / nntpgrab_core / plugins.c @ 1913

History | View | Annotate | Download (54.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.config_get_config_folder = configuration_get_config_folder;
216
    obj->core_funcs.schedular_start = schedular_start_wrapper;
217
    obj->core_funcs.schedular_stop = schedular_stop_wrapper;
218
    obj->core_funcs.schedular_get_state = download_thread_get_state;
219
    obj->core_funcs.schedular_add_file_to_queue = download_queue_add_file_to_queue;
220
    obj->core_funcs.schedular_del_file_from_queue = download_queue_del_file_from_queue;
221
    obj->core_funcs.schedular_restart_file = download_queue_restart_file;
222
    obj->core_funcs.schedular_save_queue = download_queue_save;
223
    obj->core_funcs.schedular_foreach_file = download_queue_foreach_file;
224
    obj->core_funcs.schedular_move_file = download_queue_move_file;
225
    obj->core_funcs.schedular_move_collection = download_queue_move_collection;
226
    obj->core_funcs.schedular_mark_task_optional = download_queue_mark_task_optional;
227
    obj->core_funcs.plugins_get_avail_plugins = plugins_get_avail_plugins;
228
    obj->core_funcs.plugins_free_avail_plugins = plugins_free_avail_plugins;
229
    obj->core_funcs.plugins_get_plugin_info = plugins_get_plugin_info;
230
    obj->core_funcs.plugins_load_plugin = plugins_load_plugin_by_name;
231
    obj->core_funcs.plugins_unload_plugin = plugins_unload_plugin_by_name;
232
    obj->core_funcs.plugins_call_plugin_method = plugins_call_plugin_method;
233
    obj->core_funcs.plugins_set_persistent = plugins_set_persistent;
234
    obj->core_funcs.server_request_quit = server_request_quit;
235
    obj->core_funcs.set_emit_log_messages = plugins_set_emit_log_messages_to_plugins;
236
}
237

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

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

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

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

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

                
276
    g_slice_free(NGPluginCoreData, plugin_data->core_data);
277
    plugin_data->core_data = NULL;
278
}
279

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

                
285
    gobject_class->finalize = ng_plugin_finalize;
286
}
287

                
288
static gboolean
289
bind_plugin_function(GModule *plugin, const char *function_name, gpointer *symbol)
290
{
291
    if (!g_module_symbol(plugin, function_name, symbol)) {
292
        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));
293
        return FALSE;
294
    }
295

                
296
    return TRUE;
297
}
298

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

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

                
310
    ng_plugin_set_required_function(plugin_data, "decode_file");
311

                
312
    plugin_data->core_data->is_required = TRUE;
313

                
314
    all_available_plugins = g_list_append(all_available_plugins, plugin_data);
315
}
316

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

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

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

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

                
343
        g_free(complete_path);
344
        return;
345
    }
346
    g_free(complete_path);
347

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

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

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

                
364
        g_object_unref(plugin_data);
365

                
366
        return;
367
    }
368

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

                
381
        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);
382

                
383
        g_object_unref(plugin_data);
384

                
385
        return;
386
    }
387

                
388
    /* Allow the plugin to indicate which functions and events are needed or exported */
389
    plugin_data->core_data->init_func(plugin_data);
390

                
391
    /* Plugin initialised succesfully */
392
    all_available_plugins = g_list_append(all_available_plugins, plugin_data);
393

                
394
    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, __FILE__ ":%i successfully loaded plugin '%s'", __LINE__, g_module_name(plugin));
395
}
396

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

                
404
    if (plugin_data->core_data->load_func &&
405
        !plugin_data->core_data->load_func(plugin_data, errmsg)) {
406

                
407
        return FALSE;
408
    }
409

                
410
    return TRUE;
411
}
412

                
413
static gboolean
414
load_plugin_functions(NGPlugin *plugin_data)
415
{
416
    GList *list;
417

                
418
    g_return_val_if_fail(function_plugin_data != NULL, FALSE);
419
    g_return_val_if_fail(plugin_data != NULL, FALSE);
420

                
421
    /* Register all the functions */
422
    list = plugin_data->core_data->exported_functions;
423
    while (list) {
424
        NgPluginFunctionOrEventPtr *func_ptr = list->data;
425

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

                
436
        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);
437
        g_signal_connect_swapped(function_plugin_data, func_ptr->function_or_event_name, G_CALLBACK(func_ptr->impl), plugin_data);
438

                
439
        list = g_list_next(list);
440
    }
441

                
442
    return TRUE;
443
}
444

                
445
static gboolean
446
load_plugin_events(NGPlugin *plugin_data)
447
{
448
    GList *list;
449
    int num_params;
450

                
451
    g_return_val_if_fail(plugin_data != NULL, FALSE);
452

                
453
    /* Register all the events */
454
    list = plugin_data->core_data->exported_events;
455
    while (list) {
456
        NgPluginFunctionOrEventPtr *func_ptr = list->data;
457

                
458
        if (func_ptr->is_plugin_event) {
459
            num_params = 2;
460
        } else {
461
            num_params = func_ptr->num_params;
462
        }
463

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

                
474
        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);
475

                
476
        list = g_list_next(list);
477
    }
478

                
479
    return TRUE;
480
}
481

                
482
NGPlugin *
483
get_plugin_by_name(const char *name)
484
{
485
    GList *list = all_available_plugins;
486

                
487
    while (list) {
488
        NGPlugin *plugin_data = list->data;
489

                
490
        if (!strcmp(plugin_data->core_data->name, name)) {
491
            return plugin_data;
492
        }
493

                
494
        list = g_list_next(list);
495
    }
496

                
497
    /* No plugin found */
498

                
499
    return NULL;
500
}
501

                
502
static gboolean
503
unload_plugin(NGPlugin *plugin_data, gboolean forced, char **errmsg)
504
{
505
    g_return_val_if_fail(plugin_data != NULL, FALSE);
506

                
507
    /* FIXME: Perform a dependency check */
508

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

                
514
    if (plugin_data->core_data->can_unload_func && !plugin_data->core_data->can_unload_func(plugin_data, errmsg)) {
515
        if (!forced) {
516
            return FALSE;
517
        }
518
    }
519

                
520
    if (plugin_data->core_data->unload_func) {
521
        plugin_data->core_data->unload_func(plugin_data);
522
    }
523
    plugin_data->core_data->is_loaded = FALSE;
524

                
525
    /* FIXME: Implement proper un-registering of functions and events */
526

                
527
    nntpgrab_core_emit_plugin_unloaded(FALSE, plugin_data->core_data->name);
528

                
529
    return TRUE;
530
}
531

                
532
static void
533
destroy_plugin(NGPlugin *plugin_data)
534
{
535
    GList *list;
536
    char *errmsg = NULL;
537

                
538
    g_return_if_fail(plugin_data != NULL);
539

                
540
    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);
541
    if (!unload_plugin(plugin_data, TRUE, &errmsg)) {
542
        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);
543
        g_free(errmsg);
544
    }
545

                
546
    if (plugin_data->core_data->destroy_func) {
547
        plugin_data->core_data->destroy_func(plugin_data);
548
    }
549

                
550
    all_available_plugins = g_list_remove(all_available_plugins, plugin_data);
551

                
552
    list = all_connected_events;
553
    while (list) {
554
        struct _connected_event *connection = list->data;
555

                
556
        if (connection->plugin_data == plugin_data) {
557
            g_slice_free(struct _connected_event, connection);
558
            all_connected_events = g_list_remove(all_connected_events, connection);
559
            list = all_connected_events;
560
            continue;
561
        }
562

                
563
        list = g_list_next(list);
564
    }
565

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

                
568
    g_object_unref(plugin_data);
569
}
570

                
571
void
572
destroy_plugin_by_name(const char *name)
573
{
574
    GList *list = all_available_plugins;
575

                
576
    while (list) {
577
        NGPlugin *plugin_data = list->data;
578

                
579
        if (!strcmp(plugin_data->core_data->name, name)) {
580
            destroy_plugin(plugin_data);
581
            return;
582
        }
583

                
584
        list = g_list_next(list);
585
    }
586

                
587
    /* No plugin found */
588
}
589

                
590
void
591
plugins_initialize(void)
592
{
593
    g_return_if_fail(function_plugin_data == NULL);
594
    g_return_if_fail(all_available_plugins == NULL);
595
    g_return_if_fail(all_plugin_events == NULL);
596

                
597
    function_plugin_data = g_object_new(NG_TYPE_PLUGIN, NULL);
598
    all_plugin_events = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
599

                
600
    init_internal_plugin();
601
}
602

                
603
gboolean
604
plugins_populate_list(char **errmsg)
605
{
606
    const char *filename;
607
    const char *path;
608
    GDir *dir;
609
    GError *err = NULL;
610

                
611
    g_return_val_if_fail(function_plugin_data != NULL, FALSE);
612
    g_return_val_if_fail(all_available_plugins != NULL, FALSE);
613
    g_return_val_if_fail(all_plugin_events != NULL, FALSE);
614

                
615
    if (g_getenv("NNTPGRAB_LIBDIR")) {
616
        path = g_getenv("NNTPGRAB_LIBDIR");
617
    } else {
618
        path = PLUGIN_DIR;
619
    }
620

                
621
    dir = g_dir_open(path, 0, &err);
622

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

                
630
    while ((filename = g_dir_read_name(dir))) {
631
        try_to_init_plugin(path, filename);
632
    }
633

                
634
    g_dir_close(dir);
635

                
636
    return TRUE;
637
}
638

                
639
static const char *
640
ng_plugins_find_plugin_function_or_event(const char *function_or_event_name)
641
{
642
    GList *list_plugins = all_available_plugins;
643

                
644
    while (list_plugins) {
645
        NGPlugin *plugin_data = list_plugins->data;
646
        GList *list_functions = plugin_data->core_data->exported_functions;
647
        GList *list_events = plugin_data->core_data->exported_events;
648

                
649
        while (list_functions) {
650
            NgPluginFunctionOrEventPtr *func_ptr = list_functions->data;
651

                
652
            if (!strcmp(function_or_event_name, func_ptr->function_or_event_name)) {
653
                return plugin_data->core_data->name;
654
            }
655

                
656
            list_functions = g_list_next(list_functions);
657
        }
658

                
659
        while (list_events) {
660
            NgPluginFunctionOrEventPtr *func_ptr = list_events->data;
661

                
662
            if (!strcmp(function_or_event_name, func_ptr->function_or_event_name)) {
663
                return plugin_data->core_data->name;
664
            }
665

                
666
            list_events = g_list_next(list_events);
667
        }
668

                
669
        list_plugins = g_list_next(list_plugins);
670
    }
671

                
672
    /* No plugin found providing the given function */
673
    return NULL;
674
}
675

                
676
static void
677
ng_plugins_add_dependency(NGPluginCoreData *core_data, const char *plugin_name)
678
{
679
    GList *list = core_data->dependencies;
680

                
681
    /* Is this dependency already registered? */
682
    while (list) {
683
        char *name = list->data;
684

                
685
        if (!strcmp(name, plugin_name)) {
686
            /* Yes it is, bail out */
687
            return;
688
        }
689

                
690
        list = g_list_next(list);
691
    }
692

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

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

                
699
static gboolean
700
test_all_plugin_deps_found(GList *ordered_list, NGPlugin *plugin_data)
701
{
702
    GList *list_deps = plugin_data->core_data->dependencies;
703

                
704
    while (list_deps) {
705
        const char *plugin_name = list_deps->data;
706
        GList *list_loaded = ordered_list;
707
        gboolean dep_found = FALSE;
708

                
709
        while (list_loaded) {
710
            NGPlugin *plugin_data_loaded = list_loaded->data;
711

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

                
718
            list_loaded = g_list_next(list_loaded);
719
        }
720

                
721
        if (!dep_found) {
722
            return FALSE;
723
        }
724

                
725
        list_deps = g_list_next(list_deps);
726
    }
727

                
728
    /* All dependencies are already resolved */
729
    return TRUE;
730
}
731

                
732
static gboolean
733
test_if_plugin_is_needed_by_another_plugin(NGPlugin *plugin_to_test)
734
{
735
    GList *list = all_available_plugins;
736

                
737
    g_return_val_if_fail(plugin_to_test != NULL, FALSE);
738

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

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

                
749
        while (list_deps) {
750
            const char *plugin_name = (const char*) list_deps->data;
751

                
752
            if (!strcmp(plugin_name, plugin_to_test->core_data->name)) {
753
                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);
754
                plugin_to_test->core_data->is_required = TRUE;
755
                return TRUE;
756
            }
757

                
758
            list_deps = g_list_next(list_deps);
759
        }
760

                
761
        list = g_list_next(list);
762
    }
763

                
764
    plugin_to_test->core_data->is_required = FALSE;
765
    return FALSE;
766
}
767

                
768
static int
769
calculate_num_required_plugins(void)
770
{
771
    GList *list = all_available_plugins;
772
    int count = 0;
773

                
774
    while (list) {
775
        NGPlugin *plugin_data = list->data;
776

                
777
        if (plugin_data->core_data->is_required) {
778
            count++;
779
        }
780

                
781
        list = g_list_next(list);
782
    }
783

                
784
    return count;
785
}
786

                
787
static void
788
log_dependency_tree(GList *ordered_list)
789
{
790
    char list_str[1024];
791
    GList *list;
792

                
793
    memset(&list_str, 0, sizeof(list_str));
794
    list = ordered_list;
795
    while (list) {
796
        NGPlugin *plugin_data = list->data;
797

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

                
805
        list = g_list_next(list);
806
    }
807
    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "Ordered list = %s", list_str);
808

                
809
    memset(&list_str, 0, sizeof(list_str));
810
    list = all_available_plugins;
811
    while (list) {
812
        NGPlugin *plugin_data = list->data;
813

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

                
821
        list = g_list_next(list);
822
    }
823
    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "All available plugins = %s", list_str);
824
}
825

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

                
834
    /* Find out which plugins depend on each other */
835
    while (list_plugins) {
836
        NGPlugin *plugin_data = list_plugins->data;
837

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

                
844
            plugin_name = ng_plugins_find_plugin_function_or_event(function_or_event_name);
845
            if (!plugin_name) {
846
                plugin_data->core_data->initialisation_error = TRUE;
847
                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);
848
                ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_FATAL, "%s", plugin_data->core_data->initialisation_errmsg);
849
                break;
850
            }
851

                
852
            ng_plugins_add_dependency(plugin_data->core_data, plugin_name);
853

                
854
            list_funcs_and_events_required = g_list_next(list_funcs_and_events_required);
855
        }
856

                
857
        list_plugins = g_list_next(list_plugins);
858
    }
859

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

                
868
        num_iterations++;
869
        ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "Iteration %i in dependency resolving", num_iterations);
870

                
871
        while (list_plugins) {
872
            NGPlugin *plugin_data = list_plugins->data;
873

                
874
            if (!plugin_data->core_data->initialisation_error &&
875
                !g_list_find(ordered_list, plugin_data) &&
876
                test_all_plugin_deps_found(ordered_list, plugin_data)) {
877

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

                
882
                    expected_length = calculate_num_required_plugins();
883
                    ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_DEBUG, "Expected length of ordered list updated to %i", expected_length);
884

                
885
                    continue;
886
                }
887

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

                
893
            list_plugins = g_list_next(list_plugins);
894
        }
895

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

                
902
            ng_plugin_emit_log_msg(NULL, NG_LOG_LEVEL_FATAL, "Unable to resolve dependency order");
903

                
904
            log_dependency_tree(ordered_list);
905

                
906
            return NULL;
907
        }
908
    }
909

                
910
    log_dependency_tree(ordered_list);
911

                
912
    return ordered_list;
913
}
914

                
915
gboolean
916
plugins_load_plugin(NGPlugin *plugin_data, char **errmsg)
917
{
918
    g_return_val_if_fail(plugin_data != NULL, FALSE);
919

                
920
    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);
921

                
922
    /* TODO: Test if all dependencies are already loaded */
923

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

                
931
    if (!load_plugin_base(plugin_data, errmsg) ||
932
        !load_plugin_functions(plugin_data) ||
933
        !load_plugin_events(plugin_data)) {
934

                
935
        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);
936

                
937
        return FALSE;
938
    } else {
939
        plugin_data->core_data->is_loaded = TRUE;
940
        nntpgrab_core_emit_plugin_loaded(FALSE, plugin_data->core_data->name, TRUE);
941

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

                
944
        return TRUE;
945
    }
946
}
947

                
948
gboolean
949
plugins_load(NntpgrabCore *core, Configuration *configuration, char **errmsg)
950
{
951
    GList *list = NULL;
952
    GList *ordered_list = NULL;
953

                
954
    g_return_val_if_fail(configuration != NULL, FALSE);
955
    config = configuration;
956
    g_signal_connect(config, "config_changed", G_CALLBACK(config_changed), NULL);
957
    g_signal_connect(core, "plugin_loaded", G_CALLBACK(plugin_loaded), NULL);
958
    config_changed(config, NULL);
959

                
960
    list = ordered_list = plugins_generate_dependency_tree(TRUE, errmsg);
961
    if (!ordered_list) {
962
        return FALSE;
963
    }
964

                
965
    while (list) {
966
        NGPlugin *plugin_data = list->data;
967

                
968
        if (!plugins_load_plugin(plugin_data, errmsg)) {
969
            g_list_free(ordered_list);
970
            return FALSE;
971
        }
972

                
973
        list = g_list_next(list);
974
    }
975

                
976
    g_list_free(ordered_list);
977

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

                
981
    return TRUE;
982
}
983

                
984
void
985
plugins_destroy(void)
986
{
987
    GList *list = NULL;
988
    GList *ordered_list = NULL;
989

                
990
    g_return_if_fail(function_plugin_data != NULL);
991
    g_return_if_fail(all_plugin_events != NULL);
992

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

                
999
    while (list) {
1000
        NGPlugin *plugin_data = list->data;
1001

                
1002
        destroy_plugin(plugin_data);
1003

                
1004
        list = g_list_next(list);
1005
    }
1006

                
1007
    g_list_free(ordered_list);
1008
    ordered_list = NULL;
1009

                
1010
    g_object_unref(function_plugin_data);
1011
    function_plugin_data = NULL;
1012

                
1013
    g_hash_table_destroy(all_plugin_events);
1014
    all_plugin_events = NULL;
1015

                
1016
    g_list_free(all_available_plugins);
1017
    all_available_plugins = NULL;
1018
}
1019

                
1020
ngboolean
1021
ng_plugin_get_is_loaded(NGPlugin *plugin_data)
1022
{
1023
    g_return_val_if_fail(plugin_data != NULL, FALSE);
1024

                
1025
    if (plugin_data->core_data && plugin_data->core_data->is_loaded) {
1026
        return TRUE;
1027
    } else {
1028
        return FALSE;
1029
    }
1030
}
1031

                
1032
void ng_plugin_set_name(NGPlugin *plugin_data, const char *name)
1033
{
1034
    g_return_if_fail(plugin_data != NULL);
1035
    g_return_if_fail(name != NULL);
1036

                
1037
    strncpy(plugin_data->core_data->name, name, sizeof(plugin_data->core_data->name) - 1);
1038
}
1039

                
1040
void ng_plugin_set_version(NGPlugin *plugin_data, const char *version)
1041
{
1042
    g_return_if_fail(plugin_data != NULL);
1043
    g_return_if_fail(version != NULL);
1044

                
1045
    strncpy(plugin_data->core_data->version, version, sizeof(plugin_data->core_data->version) - 1);
1046
}
1047

                
1048
void ng_plugin_set_author(NGPlugin *plugin_data, const char *author)
1049
{
1050
    g_return_if_fail(plugin_data != NULL);
1051
    g_return_if_fail(author != NULL);
1052

                
1053
    strncpy(plugin_data->core_data->author, author, sizeof(plugin_data->core_data->author) - 1);
1054
}
1055

                
1056
void ng_plugin_set_url(NGPlugin *plugin_data, const char *url)
1057
{
1058
    g_return_if_fail(plugin_data != NULL);
1059
    g_return_if_fail(url != NULL);
1060

                
1061
    strncpy(plugin_data->core_data->url, url, sizeof(plugin_data->core_data->url) - 1);
1062
}
1063

                
1064
void ng_plugin_set_description(NGPlugin *plugin_data, const char *description)
1065
{
1066
    g_return_if_fail(plugin_data != NULL);
1067
    g_return_if_fail(description != NULL);
1068

                
1069
    strncpy(plugin_data->core_data->description, description, sizeof(plugin_data->core_data->description) - 1);
1070
}
1071

                
1072
static ngboolean
1073
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)
1074
{
1075
    NgPluginFunctionOrEventPtr *func_ptr;
1076
    int i;
1077
    int num_params_real;
1078

                
1079
    g_return_val_if_fail(exported_list != NULL, FALSE);
1080
    g_return_val_if_fail(plugin_data != NULL, FALSE);
1081
    g_return_val_if_fail(plugin_data->core_data != NULL, FALSE);
1082
    g_return_val_if_fail(function_or_event_name != NULL, FALSE);
1083
    g_return_val_if_fail(num_params >= 0, FALSE);
1084

                
1085
    if (is_plugin_event) {
1086
        num_params_real = 2;
1087
    } else {
1088
        num_params_real = num_params;
1089
    }
1090

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

                
1100
    for (i = 0; i < num_params_real; i++) {
1101
        func_ptr->params[i] = va_arg(args, GType);
1102
    }
1103

                
1104
    *exported_list = g_list_append(*exported_list, func_ptr);
1105

                
1106
    return TRUE;
1107
}
1108

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

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

                
1120
            memset(function_name_new, 0, sizeof(function_name_new));
1121
            strcpy(function_name_new, "function_");
1122
            strncat(function_name_new, function_name, sizeof(function_name_new) - strlen(function_name_new) - 1);
1123

                
1124
            va_start(ap, num_params);
1125
            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);
1126
            va_end(ap);
1127

                
1128
            return ret;
1129
        }
1130

                
1131
        list = g_list_next(list);
1132
    }
1133

                
1134
    /* Internal plugin not found */
1135
    return FALSE;
1136
}
1137

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

                
1144
    memset(function_name_new, 0, sizeof(function_name_new));
1145
    strcpy(function_name_new, "function_");
1146
    strncat(function_name_new, function_name, sizeof(function_name_new) - strlen(function_name_new) - 1);
1147

                
1148
    va_start(ap, num_params);
1149
    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);
1150
    va_end(ap);
1151

                
1152
    return ret;
1153
}
1154

                
1155
static void
1156
ng_plugin_set_required_function_or_event(NGPlugin *plugin_data, const char *function_or_event_name)
1157
{
1158
    GList *list;
1159

                
1160
    g_return_if_fail(plugin_data != NULL);
1161
    g_return_if_fail(plugin_data->core_data != NULL);
1162
    g_return_if_fail(function_or_event_name != NULL);
1163

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

                
1169
        if (!strcmp(function_or_event_name, name)) {
1170
            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);
1171
            return;
1172
        }
1173

                
1174
        list = g_list_next(list);
1175
    }
1176

                
1177
    /* Add it to the list */
1178
    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));
1179
}
1180

                
1181
void
1182
ng_plugin_set_required_function(NGPlugin *plugin_data, const char *function_name)
1183
{
1184
    char function_name_new[128];
1185

                
1186
    memset(function_name_new, 0, sizeof(function_name_new));
1187
    strcpy(function_name_new, "function_");
1188
    strncat(function_name_new, function_name, sizeof(function_name_new) - strlen(function_name_new) - 1);
1189

                
1190
    ng_plugin_set_required_function_or_event(plugin_data, function_name_new);
1191
}
1192

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

                
1199
    g_return_val_if_fail(function_or_event != NULL, FALSE);
1200
    g_return_val_if_fail(function_or_event->core_data != NULL, FALSE);
1201
    g_return_val_if_fail(function_or_event_name != NULL, FALSE);
1202

                
1203
    if (!g_signal_parse_name(function_or_event_name, NG_TYPE_PLUGIN, &signal_id, &detail, TRUE)) {
1204
        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);
1205
        return FALSE;
1206
    }
1207

                
1208
    g_signal_emit_valist(function_or_event, signal_id, detail, ap);
1209

                
1210
    return TRUE;
1211

                
1212
}
1213

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

                
1221
    g_return_val_if_fail(function_plugin_data != NULL, FALSE);
1222
    g_return_val_if_fail(function_plugin_data->core_data != NULL, FALSE);
1223
    g_return_val_if_fail(function_name != NULL, FALSE);
1224

                
1225
    memset(function_name_new, 0, sizeof(function_name_new));
1226
    strcpy(function_name_new, "function_");
1227
    strncat(function_name_new, function_name, sizeof(function_name_new) - strlen(function_name_new) - 1);
1228

                
1229
    va_start(ap, function_name);
1230
    ret = ng_plugin_call_function_or_event_va(function_plugin_data, function_name_new, ap);
1231
    va_end(ap);
1232

                
1233
    return ret;
1234
}
1235

                
1236
static ngboolean
1237
ng_plugin_create_event_va(NGPlugin *plugin_data, const char *event_name_new, int num_params, ...)
1238
{
1239
    ngboolean ret;
1240
    va_list ap;
1241

                
1242
    va_start(ap, num_params);
1243
    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);
1244
    va_end(ap);
1245

                
1246
    return ret;
1247
}
1248

                
1249
ngboolean
1250
ng_plugin_create_event(NGPlugin *plugin_data, const char *event_name, int num_params)
1251
{
1252
    ngboolean ret;
1253
    char event_name_new[128];
1254

                
1255
    memset(event_name_new, 0, sizeof(event_name_new));
1256
    strcpy(event_name_new, "event_");
1257
    strncat(event_name_new, event_name, sizeof(event_name_new) - strlen(event_name_new) - 1);
1258

                
1259
    ret = ng_plugin_create_event_va(plugin_data, event_name_new, 2, G_TYPE_STRING, G_TYPE_STRV);
1260

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

                
1266
    return ret;
1267
}
1268

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

                
1277
    plugin_data = get_plugin_by_name("Internal");
1278
    if (!plugin_data) {
1279
        return FALSE;
1280
    }
1281

                
1282
    memset(event_name_new, 0, sizeof(event_name_new));
1283
    strcpy(event_name_new, "event_");
1284
    strncat(event_name_new, event_name, sizeof(event_name_new) - strlen(event_name_new) - 1);
1285

                
1286
    va_start(ap, num_params);
1287
    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);
1288
    va_end(ap);
1289

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

                
1295
    return ret;
1296
}
1297

                
1298
void
1299
ng_plugin_set_required_event(NGPlugin *plugin_data, const char *event_name)
1300
{
1301
    char event_name_new[128];
1302

                
1303
    memset(event_name_new, 0, sizeof(event_name_new));
1304
    strcpy(event_name_new, "event_");
1305
    strncat(event_name_new, event_name, sizeof(event_name_new) - strlen(event_name_new) - 1);
1306

                
1307
    ng_plugin_set_required_function_or_event(plugin_data, event_name_new);
1308
}
1309

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

                
1317
    g_return_val_if_fail(event_name != NULL, FALSE);
1318
    g_return_val_if_fail(impl != NULL, FALSE);
1319

                
1320
    if (!plugin_data) {
1321
        plugin_data = get_plugin_by_name("Internal");
1322
        if (!plugin_data) {
1323
            return FALSE;
1324
        }
1325
    }
1326

                
1327
    event_plugin = g_hash_table_lookup(all_plugin_events, event_name);
1328
    if (!event_plugin) {
1329
        return FALSE;
1330
    }
1331

                
1332
    memset(event_name_new, 0, sizeof(event_name_new));
1333
    strcpy(event_name_new, "event_");
1334
    strncat(event_name_new, event_name, sizeof(event_name_new) - strlen(event_name_new) - 1);
1335

                
1336
    g_signal_connect(plugin_data, event_name_new, G_CALLBACK(impl), data);
1337
    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);
1338

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

                
1347
    return TRUE;
1348
}
1349

                
1350
ngboolean
1351
ng_plugin_disconnect_event_by_func(NGPlugin *plugin_data, NGPluginFunction impl, void *data)
1352
{
1353
    GList *list;
1354

                
1355
    g_return_val_if_fail(impl != NULL, FALSE);
1356

                
1357
    list = all_connected_events;
1358
    while (list) {
1359
        struct _connected_event *connection = list->data;
1360

                
1361
        if (connection->plugin_data == plugin_data && connection->impl == impl) {
1362
            g_slice_free(struct _connected_event, connection);
1363
            all_connected_events = g_list_remove(all_connected_events, connection);
1364

                
1365
            g_signal_handlers_disconnect_by_func(plugin_data, G_CALLBACK(impl), data);
1366

                
1367
            return TRUE;
1368
        }
1369

                
1370
        list = g_list_next(list);
1371
    }
1372

                
1373
    return FALSE;
1374
}
1375

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

                
1385
    g_return_val_if_fail(event_name != NULL, FALSE);
1386

                
1387
    event_name_hash = g_str_hash(event_name);
1388

                
1389
    memset(event_name_new, 0, sizeof(event_name_new));
1390
    strcpy(event_name_new, "event_");
1391
    strncat(event_name_new, event_name, sizeof(event_name_new) - strlen(event_name_new) - 1);
1392

                
1393
    list = all_connected_events;
1394
    while (list) {
1395
        struct _connected_event *connection = list->data;
1396

                
1397
        if (connection->event_name_hash == event_name_hash &&
1398
            !strcmp(connection->event_name, event_name)) {
1399

                
1400
            va_start(ap, event_name);
1401
            ret = ng_plugin_call_function_or_event_va(connection->plugin_data, event_name_new, ap);
1402
            va_end(ap);
1403

                
1404
#if 0 
1405
            if (!ret) {
1406
                break;
1407
            }
1408
#endif
1409
        }
1410

                
1411
        list = g_list_next(list);
1412
    }
1413

                
1414
    return ret;
1415
}
1416

                
1417
ngboolean
1418
ng_plugin_emit_event(NGPlugin *plugin_data, const char *event_name, const char **params)
1419
{
1420
    g_return_val_if_fail(plugin_data != NULL, FALSE);
1421
    g_return_val_if_fail(event_name != NULL, FALSE);
1422

                
1423
    if (!ng_plugin_emit_internal_event(event_name, plugin_data->core_data->name, params)) {
1424
        return FALSE;
1425
    }
1426

                
1427
    nntpgrab_core_emit_plugin_event(FALSE, plugin_data->core_data->name, event_name, params);
1428

                
1429
    return TRUE;
1430
}
1431

                
1432
static gboolean print_logging = FALSE;
1433
static gboolean emit_logging_to_frontend = FALSE;
1434
static gboolean emit_logging_to_plugins = FALSE;
1435
static gboolean enable_logger_plugin = FALSE;
1436
static gboolean logger_plugin_loaded = FALSE;
1437

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

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

                
1453
void
1454
plugins_print_logging(void)
1455
{
1456
    print_logging = TRUE;
1457
}
1458

                
1459
void
1460
plugins_set_emit_log_messages_to_frontend(ngboolean val)
1461
{
1462
    emit_logging_to_frontend = val;
1463
}
1464

                
1465
static void
1466
plugins_set_emit_log_messages_to_plugins(ngboolean val)
1467
{
1468
    emit_logging_to_plugins = val;
1469
}
1470

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

                
1478
    g_return_if_fail(log_level != NG_LOG_LEVEL_ALL);
1479
    g_return_if_fail(format != NULL);
1480

                
1481
    if (!emit_logging_to_frontend && !emit_logging_to_plugins && !enable_logger_plugin && !print_logging) {
1482
        return;
1483
    }
1484

                
1485
    if (plugin_data && plugin_data->core_data && plugin_data->core_data->name) {
1486
        component = plugin_data->core_data->name;
1487
    } else {
1488
        component = "NNTPGrab Core";
1489
    }
1490

                
1491
    va_start(args, format);
1492
    memset(msg, 0, sizeof(msg));
1493
    vsnprintf(msg, sizeof(msg) - 1, format, args);
1494
    va_end(args);
1495

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

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

                
1508
        case NG_LOG_LEVEL_DEBUG:
1509
            case NG_LOG_LEVEL_INFO:
1510
            default:
1511
                level = G_LOG_LEVEL_INFO;
1512
        }
1513

                
1514
        va_start(args, format);
1515
        g_logv(component, level, format, args);
1516
        va_end(args);
1517
    }
1518

                
1519
    if (emit_logging_to_frontend || emit_logging_to_plugins || (enable_logger_plugin && logger_plugin_loaded)) {
1520
        nntpgrab_core_emit_log_message(FALSE, component, log_level, msg, emit_logging_to_frontend, (emit_logging_to_plugins || (enable_logger_plugin && logger_plugin_loaded)));
1521
    }
1522
}
1523

                
1524
NGList *
1525
ng_plugin_get_avail_plugins()
1526
{
1527
    NGList *ret = NULL;
1528
    GList *list = all_available_plugins;
1529

                
1530
    while (list) {
1531
        NGPlugin *plugin_data = list->data;
1532
        NGPluginInfo *plugin_info = g_slice_new0(NGPluginInfo);
1533
        GList *list2;
1534

                
1535
        plugin_info->is_loaded = plugin_data->core_data->is_loaded;
1536
        strncpy(plugin_info->name, plugin_data->core_data->name, sizeof(plugin_data->core_data->name) - 1);
1537
        strncpy(plugin_info->version, plugin_data->core_data->version, sizeof(plugin_data->core_data->version) - 1);
1538
        strncpy(plugin_info->author, plugin_data->core_data->author, sizeof(plugin_data->core_data->author) - 1);
1539
        strncpy(plugin_info->url, plugin_data->core_data->url, sizeof(plugin_data->core_data->url) - 1);
1540
        strncpy(plugin_info->description, plugin_data->core_data->description, sizeof(plugin_data->core_data->description) - 1);
1541

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

                
1548
        list = g_list_next(list);
1549
    }
1550

                
1551
    return ret;
1552
}
1553

                
1554
void
1555
ng_plugin_free_avail_plugins(NGList *list_avail_plugins)
1556
{
1557
    NGList *list = list_avail_plugins;
1558

                
1559
    while (list) {
1560
        NGPluginInfo *plugin_info = list->data;
1561
        NGList *list2;
1562

                
1563
        list2 = plugin_info->dependencies;
1564
        while (list2) {
1565
            g_free(list2->data);
1566
            list2 = ng_list_next(list2);
1567
        }
1568

                
1569
        ng_list_free(plugin_info->dependencies);
1570
        g_slice_free(NGPluginInfo, plugin_info);
1571

                
1572
        list = ng_list_next(list);
1573
    }
1574

                
1575
    ng_list_free(list_avail_plugins);
1576
}
1577

                
1578
/* Exported functions */
1579
NGList *
1580
plugins_get_avail_plugins (void)
1581
{
1582
    GList *list;
1583
    GList *list_ret = NULL;
1584

                
1585
    list = all_available_plugins;
1586
    while (list) {
1587
        NGPlugin *plugin_data = list->data;
1588

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

                
1596
            list = g_list_next(list);
1597
            continue;
1598
        }
1599

                
1600
        list_ret = g_list_append(list_ret, g_strdup(plugin_data->core_data->name));
1601

                
1602
        list = g_list_next(list);
1603
    }
1604

                
1605
    return (NGList*) list_ret;
1606
}
1607

                
1608
void
1609
plugins_free_avail_plugins (NGList *plugins)
1610
{
1611
    NGList *list;
1612

                
1613
    list = plugins;
1614
    while (list) {
1615
        g_free(list->data);
1616
        list = ng_list_next(list);
1617
    }
1618

                
1619
    g_list_free((GList*) plugins);
1620
}
1621

                
1622
ngboolean
1623
plugins_get_plugin_info (const char *plugin_name, NNTPGrabPluginInfo *plugin_info)
1624
{
1625
    GList *list = all_available_plugins;
1626

                
1627
    g_return_val_if_fail(plugin_name != NULL, FALSE);
1628
    g_return_val_if_fail(plugin_info != NULL, FALSE);
1629

                
1630
    while (list) {
1631
        NGPlugin *plugin_data = list->data;
1632

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

                
1637
            strncpy(plugin_info->name, plugin_data->core_data->name, sizeof(plugin_info->name) - 1);
1638
            strncpy(plugin_info->version, plugin_data->core_data->version, sizeof(plugin_info->version) - 1);
1639
            strncpy(plugin_info->author, plugin_data->core_data->author, sizeof(plugin_info->author) - 1);
1640
            strncpy(plugin_info->url, plugin_data->core_data->url, sizeof(plugin_info->url) - 1);
1641
            strncpy(plugin_info->description, plugin_data->core_data->description, sizeof(plugin_info->description) - 1);
1642

                
1643
            plugin_info->is_loaded = plugin_data->core_data->is_loaded;
1644
            plugin_info->is_persistent = plugin_data->core_data->is_required;
1645

                
1646
            return TRUE;
1647
        }
1648

                
1649
        list = g_list_next(list);
1650
    }
1651

                
1652
    /* Plugin not found */
1653
    return FALSE;
1654
}
1655

                
1656
ngboolean
1657
plugins_load_plugin_by_name (const char *plugin_name, char **errmsg)
1658
{
1659
    NGPlugin *plugin_data = get_plugin_by_name(plugin_name);
1660

                
1661
    g_return_val_if_fail(plugin_name != NULL, FALSE);
1662
    /* NOTE: errmsg MIGHT be NULL */
1663

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

                
1669
        return FALSE;
1670
    }
1671

                
1672
    return plugins_load_plugin(plugin_data, errmsg);
1673
}
1674

                
1675
ngboolean
1676
plugins_unload_plugin_by_name (const char *plugin_name, char **errmsg)
1677
{
1678
    NGPlugin *plugin_data = get_plugin_by_name(plugin_name);
1679

                
1680
    g_return_val_if_fail(plugin_name != NULL, FALSE);
1681
    /* NOTE: errmsg MIGHT be NULL */
1682

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

                
1688
        return FALSE;
1689
    }
1690

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

                
1701
ngboolean
1702
plugins_set_persistent (const char *plugin_name, ngboolean persistent)
1703
{
1704
    /* TODO: Needs to be implemented */
1705
    return FALSE;
1706
}
1707

                
1708
NGVariant *plugins_call_plugin_method(const char *plugin_name, const char *method, NGVariant *parameters, char **errmsg)
1709
{
1710
    NGPlugin *plugin_data;
1711
    NGVariant *ret;
1712

                
1713
    g_return_val_if_fail(plugin_name != NULL, NULL);
1714
    /* NOTE: errmsg MIGHT be NULL */
1715

                
1716
    plugin_data = get_plugin_by_name(plugin_name);
1717
    if (!plugin_data) {
1718
        if (errmsg) {
1719
            *errmsg = g_strdup_printf("No plugin found named '%s'", plugin_name);
1720
        }
1721
        return NULL;
1722
    }
1723

                
1724
    ret = plugin_data->core_data->call_plugin_method_func(plugin_data, method, parameters, errmsg);
1725

                
1726
    if (!ret && errmsg && *errmsg == NULL) {
1727
        *errmsg = g_strdup_printf("Plugin '%s' doesn't implemented method '%s'", plugin_name, method);
1728
    }
1729

                
1730
    return ret;
1731
}