Statistics
| Revision:

root / trunk / plugins / counter / plugin_counter.c @ 1919

History | View | Annotate | Download (17.2 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 "nntpgrab_plugin.h"
22
#include "nntpgrab_utils.h"
23
#include "config.h"
24

                
25
typedef struct _counter_server_data {
26
    NGConfigServer server_info;
27
    guint64 num_bytes_downloaded;
28
    guint64 num_parts_succeeded;
29
    guint64 num_parts_failed;
30
} CounterServerData;
31

                
32
typedef struct _counter_data {
33
    time_t stamp_started;
34
    gboolean data_changed_since_last_announce;
35
    GList *collected_data;     /* List contains CounterServerData elements */
36
} CounterData;
37

                
38
static void plugin_counter_free_cached_data(NGPlugin *plugin_data);
39
static gboolean plugin_counter_load_cached_data(NGPlugin *plugin_data, char **errmsg);
40
static gboolean plugin_counter_save_cached_data(NGPlugin *plugin_data, char **errmsg);
41
static gboolean plugin_counter_send_update_announcement(gpointer data);
42
static CounterServerData *plugin_counter_lookup_server(NGPlugin *plugin_data, const char *servername);
43
static void plugin_counter_part_done_cb(NGPlugin *plugin_data, const char *servername, int conn_id, const char *collection_name, const char *subject, int part_num, int size, gpointer data);
44
static void plugin_counter_part_failed_cb(NGPlugin *plugin_data, const char *servername, int conn_id, const char *collection_name, const char *subject, int part_num,int size, gboolean all_servers_tried);
45

                
46
void
47
nntpgrab_plugin_initialize(NGPlugin *plugin_data)
48
{
49
    ng_plugin_set_name(plugin_data, "Download counter");
50
    ng_plugin_set_version(plugin_data, PACKAGE_VERSION);
51
    ng_plugin_set_author(plugin_data, "Erik van Pienbroek");
52
    ng_plugin_set_url(plugin_data, "https://www.nntpgrab.nl");
53
    ng_plugin_set_description(plugin_data, "Count the amount of download traffic for each configured usenet server ");
54

                
55
    ng_plugin_create_event(plugin_data, "counter_data_changed", 0);
56

                
57
    ng_plugin_set_required_event(plugin_data, "part_done");
58
    ng_plugin_set_required_event(plugin_data, "part_failed");
59
}
60

                
61
ngboolean
62
nntpgrab_plugin_load(NGPlugin *plugin_data, char **errmsg)
63
{
64
    ng_plugin_connect_event(plugin_data, "part_done", NG_PLUGIN_FUNCTION(plugin_counter_part_done_cb), NULL);
65
    ng_plugin_connect_event(plugin_data, "part_failed", NG_PLUGIN_FUNCTION(plugin_counter_part_failed_cb), NULL);
66

                
67
    plugin_data->priv = g_slice_new0(CounterData);
68

                
69
    if (!plugin_counter_load_cached_data(plugin_data, errmsg)) {
70
        return FALSE;
71
    }
72

                
73
    /* Make sure the plugin sends update announcements every 10 seconds */
74
    g_timeout_add_seconds(10, plugin_counter_send_update_announcement, plugin_data);
75
    plugin_counter_send_update_announcement(plugin_data);
76

                
77
    return TRUE;
78
}
79

                
80
ngboolean
81
nntpgrab_plugin_can_unload(NGPlugin *plugin_data, char **reason)
82
{
83
    return TRUE;
84
}
85

                
86
void
87
nntpgrab_plugin_unload(NGPlugin *plugin_data)
88
{
89
    char *errmsg = NULL;
90

                
91
    if (!plugin_counter_save_cached_data(plugin_data, &errmsg)) {
92
        ng_plugin_emit_log_msg(plugin_data, NG_LOG_LEVEL_WARNING, errmsg);
93
        g_free(errmsg);
94
    }
95

                
96
    plugin_counter_free_cached_data(plugin_data);
97

                
98
    g_slice_free(CounterData, plugin_data->priv);
99
    plugin_data->priv = NULL;
100
}
101

                
102
void
103
nntpgrab_plugin_destroy(NGPlugin *plugin_data)
104
{
105
}
106

                
107
int
108
nntpgrab_plugin_get_version(void)
109
{
110
    return NNTPGRAB_PLUGIN_API_VERSION;
111
}
112

                
113
static NGVariant *
114
get_collected_data(NGPlugin *plugin_data, NGVariant *parameters, char **errmsg)
115
{
116
    GVariant *variant_counter;
117
    GVariant *variant_servers;
118
    GVariantBuilder *builder_counter;
119
    GVariantBuilder *builder_servers;
120
    CounterData *counter_data;
121
    NGList *servers;
122
    NGList *list;
123

                
124
    g_return_val_if_fail(plugin_data != NULL, NULL);
125

                
126
    counter_data = (CounterData*) plugin_data->priv;
127

                
128
    g_return_val_if_fail(counter_data != NULL, NULL);
129

                
130
    builder_counter = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
131
    g_variant_builder_add(builder_counter, "{sv}", "stamp_started", g_variant_new_uint64((guint64) counter_data->stamp_started));
132

                
133
    builder_servers = g_variant_builder_new(G_VARIANT_TYPE("av"));
134

                
135
    servers = plugin_data->core_funcs.config_get_avail_servers();
136
    list = servers;
137
    while (list) {
138
        const char *servername = (const char*) list->data;
139
        GVariant *variant_server;
140
        GVariantBuilder *builder_server;
141
        CounterServerData *server_data;
142

                
143
        server_data = plugin_counter_lookup_server(plugin_data, servername);
144

                
145
        g_return_val_if_fail(server_data != NULL, NULL);
146

                
147
        builder_server = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
148
        g_variant_builder_add(builder_server, "{sv}", "servername", g_variant_new_string(server_data->server_info.servername));
149
        g_variant_builder_add(builder_server, "{sv}", "num_bytes_downloaded", g_variant_new_uint64(server_data->num_bytes_downloaded));
150
        g_variant_builder_add(builder_server, "{sv}", "num_parts_succeeded", g_variant_new_uint64(server_data->num_parts_succeeded));
151
        g_variant_builder_add(builder_server, "{sv}", "num_parts_failed", g_variant_new_uint64(server_data->num_parts_failed));
152
        variant_server = g_variant_builder_end(builder_server);
153

                
154
        g_variant_builder_add(builder_servers, "v", variant_server);
155

                
156
        list = ng_list_next(list);
157
    }
158

                
159
    variant_servers = g_variant_builder_end(builder_servers);
160
    g_variant_builder_add(builder_counter, "{sv}", "collected_data", variant_servers);
161

                
162
    plugin_data->core_funcs.config_free_avail_servers(servers);
163

                
164
    variant_counter = g_variant_builder_end(builder_counter);
165
    return (NGVariant*) variant_counter;
166
}
167

                
168
static NGVariant *
169
reset_collected_data(NGPlugin *plugin_data, NGVariant *parameters, char **errmsg)
170
{
171
    plugin_counter_free_cached_data(plugin_data);
172

                
173
    /* Manually emit an update announcement so that all frontends 
174
     * will know that the collected data was reset */
175
    plugin_counter_send_update_announcement(plugin_data);
176

                
177
    return (NGVariant*) g_variant_new_boolean(TRUE);
178
}
179

                
180
NGVariant *
181
nntpgrab_plugin_call_plugin_method(NGPlugin *plugin_data, const char *method, NGVariant *parameters, char **errmsg)
182
{
183
    if (!g_strcmp0(method, "get_collected_data")) {
184
        return get_collected_data(plugin_data, parameters, errmsg);
185
    } else if (!g_strcmp0(method, "reset")) {
186
        return reset_collected_data(plugin_data, parameters, errmsg);
187
    } else {
188
        return NULL;
189
    }
190
}
191

                
192
static void
193
plugin_counter_free_cached_data(NGPlugin *plugin_data)
194
{
195
    CounterData *counter_data;
196
    GList *list;
197

                
198
    g_return_if_fail(plugin_data != NULL);
199

                
200
    counter_data = (CounterData*) plugin_data->priv;
201

                
202
    g_return_if_fail(counter_data != NULL);
203

                
204
    list = counter_data->collected_data;
205
    while (list) {
206
        CounterServerData *server_data = (CounterServerData*) list->data;
207

                
208
        g_slice_free(CounterServerData, server_data);
209

                
210
        list = g_list_next(list);
211
    }
212

                
213
    g_list_free(counter_data->collected_data);
214
    counter_data->collected_data = NULL;
215
    counter_data->stamp_started = time(NULL);
216

                
217
    counter_data->data_changed_since_last_announce = TRUE;
218
}
219

                
220
static gboolean
221
plugin_counter_load_cached_data(NGPlugin *plugin_data, char **errmsg)
222
{
223
    CounterData *counter_data;
224
    GKeyFile *keyfile;
225
    char *config_dir;
226
    char *filename;
227
    GError *err = NULL;
228
    char **groups;
229
    int i;
230

                
231
    g_return_val_if_fail(plugin_data != NULL, FALSE);
232
    g_return_val_if_fail(errmsg != NULL, FALSE);
233

                
234
    counter_data = (CounterData*) plugin_data->priv;
235

                
236
    g_return_val_if_fail(counter_data != NULL, FALSE);
237

                
238
    counter_data->stamp_started = time(NULL);
239

                
240
    config_dir = plugin_data->core_funcs.config_get_config_folder();
241
    g_return_val_if_fail(config_dir != NULL, FALSE);
242

                
243
    filename = g_build_filename(config_dir, "plugin_counter.conf", NULL);
244
    ng_free(config_dir);
245

                
246
    if (!g_file_test(filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
247
        /* No cached configuration file exists yet */
248
        return TRUE;
249
    }
250

                
251
    /* Clean up old entries */
252
    plugin_counter_free_cached_data(plugin_data);
253

                
254
    /* Load the contents of the configuration file from disk */
255
    keyfile = g_key_file_new();
256
    if (!g_key_file_load_from_file(keyfile, filename, G_KEY_FILE_NONE, &err)) {
257
        *errmsg = g_strdup_printf("Unable to load file '%s': %s", filename, err->message);
258
        g_error_free(err);
259
        g_free(filename);
260
        return FALSE;
261
    }
262
    g_free(filename);
263

                
264
    groups = g_key_file_get_groups(keyfile, NULL);
265
    i = 0;
266
    while (groups && groups[i] != NULL) {
267
        CounterServerData *server_data;
268
        NGConfigServer server_info;
269

                
270
        /* Only read the stamp_started key from the generic group */
271
        if (!g_strcmp0(groups[i], "generic")) {
272
            if (!g_key_file_has_key(keyfile, groups[i], "stamp_started", NULL)) {
273
                counter_data->stamp_started = time(NULL);
274
            } else {
275
                counter_data->stamp_started = (time_t) g_key_file_get_uint64(keyfile, groups[i], "stamp_started", NULL);
276
            }
277

                
278
            i++;
279
            continue;
280
        }
281

                
282
        if (!g_key_file_has_key(keyfile, groups[i], "num_bytes_downloaded", NULL) ||
283
            !g_key_file_has_key(keyfile, groups[i], "num_parts_succeeded", NULL) ||
284
            !g_key_file_has_key(keyfile, groups[i], "num_parts_failed", NULL)) {
285

                
286
            /* One or more keys weren't found */
287
            ng_plugin_emit_log_msg(plugin_data, NG_LOG_LEVEL_INFO, "Server named '%s' was found in counter plugin configuration file, but is missing some keys", groups[i]);
288
            i++;
289
            continue;
290
        }
291

                
292
        /* All required keys are found */
293

                
294
        if (!plugin_data->core_funcs.config_get_server_info(groups[i], &server_info)) {
295
            /* Server doesn't exist in current NNTPGrab configuration */
296
            ng_plugin_emit_log_msg(plugin_data, NG_LOG_LEVEL_INFO, "Server named '%s' was found in counter plugin configuration file, but isn't known in the NNTPGrab configuration", groups[i]);
297
            i++;
298
            continue;
299
        }
300

                
301
        server_data = g_slice_new0(CounterServerData);
302

                
303
        server_data->server_info = server_info;
304
        server_data->num_bytes_downloaded = g_key_file_get_uint64(keyfile, groups[i], "num_bytes_downloaded", NULL);
305
        server_data->num_parts_succeeded = g_key_file_get_uint64(keyfile, groups[i], "num_parts_succeeded", NULL);
306
        server_data->num_parts_failed = g_key_file_get_uint64(keyfile, groups[i], "num_parts_failed", NULL);
307

                
308
        counter_data->collected_data = g_list_append(counter_data->collected_data, server_data);
309

                
310
        i++;
311
    }
312

                
313
    g_key_file_free(keyfile);
314

                
315
    return TRUE;
316
}
317

                
318
static gboolean
319
plugin_counter_save_cached_data(NGPlugin *plugin_data, char **errmsg)
320
{
321
    GKeyFile *keyfile;
322
    char *config_dir;
323
    char *filename;
324
    GError *err = NULL;
325
    CounterData *counter_data = (CounterData*) plugin_data->priv;
326
    GList *list;
327
    char *contents;
328

                
329
    g_return_val_if_fail(plugin_data != NULL, FALSE);
330
    g_return_val_if_fail(errmsg != NULL, FALSE);
331
    g_return_val_if_fail(counter_data != NULL, FALSE);
332

                
333
    keyfile = g_key_file_new();
334

                
335
    g_key_file_set_uint64(keyfile, "generic", "stamp_started", (guint64) counter_data->stamp_started);
336

                
337
    list = counter_data->collected_data;
338
    while (list) {
339
        CounterServerData *server_data = (CounterServerData*) list->data;
340

                
341
        g_key_file_set_uint64(keyfile, server_data->server_info.servername, "num_bytes_downloaded", server_data->num_bytes_downloaded);
342
        g_key_file_set_uint64(keyfile, server_data->server_info.servername, "num_parts_succeeded", server_data->num_parts_succeeded);
343
        g_key_file_set_uint64(keyfile, server_data->server_info.servername, "num_parts_failed", server_data->num_parts_failed);
344

                
345
        list = g_list_next(list);
346
    }
347

                
348
    contents = g_key_file_to_data(keyfile, NULL, &err);
349
    if (!contents) {
350
        *errmsg = g_strdup(err->message);
351
        g_error_free(err);
352
        g_free(contents);
353
        g_key_file_free(keyfile);
354

                
355
        return FALSE;
356
    }
357

                
358
    /* Write the configuration to disk */
359
    config_dir = plugin_data->core_funcs.config_get_config_folder();
360
    g_return_val_if_fail(config_dir != NULL, FALSE);
361
    filename = g_build_filename(config_dir, "plugin_counter.conf", NULL);
362
    g_free(config_dir);
363

                
364
    if (!g_file_set_contents(filename, contents, -1, &err)) {
365
        *errmsg = g_strdup_printf("Error while saving download counter configuration to file '%s'\n%s", filename, err->message);
366
        g_error_free(err);
367
        g_free(contents);
368
        g_free(filename);
369
        g_key_file_free(keyfile);
370

                
371
        return FALSE;
372
    }
373

                
374
    g_free(contents);
375
    g_free(filename);
376
    g_key_file_free(keyfile);
377

                
378
    return TRUE;
379
}
380

                
381
static gboolean
382
plugin_counter_send_update_announcement(gpointer data)
383
{
384
    static int times_been_here = 0;
385
    CounterData *counter_data;
386
    NGPlugin *plugin_data = (NGPlugin*) data;
387

                
388
    g_return_val_if_fail(plugin_data != NULL, FALSE);
389
    g_return_val_if_fail(plugin_data->priv != NULL, FALSE);
390

                
391
    counter_data = (CounterData*) plugin_data->priv;
392

                
393
    /* Only send a new announcement when something got changed */
394
    if (!counter_data->data_changed_since_last_announce) {
395
        return TRUE;
396
    }
397

                
398
    ng_plugin_emit_event(plugin_data, "counter_data_changed", NULL);
399
    counter_data->data_changed_since_last_announce = FALSE;
400

                
401
    /* Periodically (every 60 seconds) save the cached data back to disk */
402
    if (times_been_here % 6 == 0) {
403
        char *errmsg = NULL;
404

                
405
        if (!plugin_counter_save_cached_data(plugin_data, &errmsg)) {
406
            ng_plugin_emit_log_msg(plugin_data, NG_LOG_LEVEL_WARNING, errmsg);
407
            g_free(errmsg);
408
        }
409
    }
410
    times_been_here++;
411

                
412
    return TRUE;
413
}
414

                
415
static CounterServerData*
416
plugin_counter_lookup_server(NGPlugin *plugin_data, const char *servername)
417
{
418
    CounterData *counter_data;
419
    CounterServerData *server_data;
420
    GList *list;
421

                
422
    g_return_val_if_fail(plugin_data != NULL, NULL);
423
    g_return_val_if_fail(servername != NULL, NULL);
424
    g_return_val_if_fail(plugin_data->priv != NULL, NULL);
425

                
426
    counter_data = (CounterData*) plugin_data->priv;
427

                
428
    list = counter_data->collected_data;
429
    while (list) {
430
        server_data = list->data;
431

                
432
        g_return_val_if_fail(server_data != NULL, NULL);
433

                
434
        if (!g_strcmp0(server_data->server_info.servername, servername)) {
435
            return server_data;
436
        }
437

                
438
        list = g_list_next(list);
439
    }
440

                
441
    /* If we got here then there's no entry available for this server. Add it now */
442
    server_data = g_slice_new0(CounterServerData);
443
    g_return_val_if_fail(plugin_data->core_funcs.config_get_server_info(servername, &server_data->server_info), NULL);
444

                
445
    server_data->num_bytes_downloaded = 0;
446
    server_data->num_parts_succeeded = 0;
447
    server_data->num_parts_failed = 0;
448

                
449
    counter_data->collected_data = g_list_append(counter_data->collected_data, server_data);
450

                
451
    return server_data;
452
}
453

                
454
static void
455
plugin_counter_part_done_cb(NGPlugin *plugin_data, const char *servername, int conn_id, const char *collection_name, const char *subject, int part_num, int size, gpointer data)
456
{
457
    CounterData *counter_data;
458
    CounterServerData *server_data;
459

                
460
    /* We only process the part_done event as this is the only event which contains both 
461
     * the servername and the size of the part. The event traffic_monitor_update contains
462
     * a more reliable amount of traffic transferred, but doesn't show the per-server
463
     * traffic which makes it unsuitable for our goal.
464
     *
465
     * When only processing this event we will lose some traffic data (for missing parts
466
     * for example) but that amount of traffic should be low so it should be okay to
467
     * ignore that type of traffic */
468
    g_return_if_fail(plugin_data != NULL);
469
    g_return_if_fail(servername != NULL);
470
    g_return_if_fail(size >= 0);
471
    g_return_if_fail(plugin_data->priv != NULL);
472

                
473
    counter_data = (CounterData*) plugin_data->priv;
474
    server_data = plugin_counter_lookup_server(plugin_data, servername);
475

                
476
    g_return_if_fail(server_data != NULL);
477

                
478
    counter_data->data_changed_since_last_announce = TRUE;
479
    server_data->num_bytes_downloaded += size;
480
    server_data->num_parts_succeeded++;
481
}
482

                
483
static void
484
plugin_counter_part_failed_cb(NGPlugin *plugin_data, const char *servername, int conn_id, const char *collection_name, const char *subject, int part_num,int size, gboolean all_servers_tried)
485
{
486
    CounterData *counter_data;
487
    CounterServerData *server_data;
488

                
489
    g_return_if_fail(plugin_data != NULL);
490
    g_return_if_fail(servername != NULL);
491
    g_return_if_fail(plugin_data->priv != NULL);
492

                
493
    counter_data = (CounterData*) plugin_data->priv;
494
    server_data = plugin_counter_lookup_server(plugin_data, servername);
495

                
496
    g_return_if_fail(server_data != NULL);
497

                
498
    counter_data->data_changed_since_last_announce = TRUE;
499
    server_data->num_parts_failed++;
500
}