Statistics
| Revision:

root / trunk / client / gui / info.c @ 1859

History | View | Annotate | Download (32 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 "gui.h"
20
#include 
21
#ifdef HAVE_SOUP
22
#include 
23
#ifdef HAVE_SOUP_GNOME
24
#include 
25
#endif
26
#else
27
#include 
28
#include 
29
#include 
30
#endif
31
#include 
32
#ifdef WIN32
33
#include 
34
#endif
35

                
36
#if defined(WIN32) && !defined(__MINGW64__)
37
/* Win32 likes to add a extra _ to external variables. Workaround this */
38
#define __BUILD_SVN_REV _BUILD_SVN_REV
39
#define __BUILD_DATE _BUILD_DATE
40
#define __BUILD_NUMBER _BUILD_NUMBER
41
#endif
42

                
43
#ifdef DARWIN
44
#define GET_BUILD_SVN_REV    __BUILD_SVN_REV
45
#define GET_BUILD_DATE       __BUILD_DATE
46
#define GET_BUILD_NUMBER     __BUILD_NUMBER
47
#else
48
extern char   __BUILD_SVN_REV;
49
extern char   __BUILD_DATE;
50
extern char   __BUILD_NUMBER;
51
#define GET_BUILD_SVN_REV    &__BUILD_SVN_REV
52
#define GET_BUILD_DATE       &__BUILD_DATE
53
#define GET_BUILD_NUMBER     &__BUILD_NUMBER
54
#endif
55

                
56
#ifndef HAVE_SOUP
57
static size_t
58
curl_write_function(void *buffer, size_t size, size_t nmemb, void *stream)
59
{
60
    char *buf;
61
    GString *data = (GString *) stream;
62

                
63
    g_assert(data);
64

                
65
    /* The buffer isn't \0 terminated. Solve this by 
66
       using a temporary buffer */
67
    buf = (char *) g_slice_alloc((int) (size * nmemb) + 1);
68
    memcpy(buf, buffer, size * nmemb);
69
    buf[size * nmemb] = '\0';
70
    data = g_string_append(data, buf);
71
    g_slice_free1((size * nmemb) + 1, buf);
72

                
73
    return (int) (size * nmemb);
74
}
75
#endif
76

                
77
static gboolean
78
open_uri (GtkWidget *embed, const char *url_unquoted, gpointer data)
79
{
80
    if (strstr(url_unquoted, "https://")) {
81
        char *url = g_shell_quote(url_unquoted);
82
#ifdef WIN32
83
        GtkWidget *windowMain = nntpgrab_gui_base_get_widget("windowMain");
84
        ShellExecuteA(GDK_WINDOW_HWND(windowMain->window), "open", url_unquoted, NULL, NULL, SW_SHOW);
85
#elif defined(DARWIN)
86
        GError *error = NULL;
87
        char cmd[1024];
88

                
89
        snprintf(cmd, sizeof(cmd), "open %s", url);
90
        g_spawn_command_line_async(cmd, &error);
91
        if (error) {
92
            g_print("%s: %s\n", error->message, cmd);
93
        }
94
#else
95
        GError *error = NULL;
96
        char cmd[1024];
97

                
98
        if (g_find_program_in_path("xdg-open")) {
99
            // We hebben de desktop-onafhankelijke methode om programma's te openen
100
            snprintf(cmd, sizeof(cmd), "xdg-open %s", url);
101
        } else if (g_getenv("GNOME_DESKTOP_SESSION_ID") && g_find_program_in_path("gnome-open")) {
102
            // We draaien Gnome
103
            snprintf(cmd, sizeof(cmd), "gnome-open %s", url);
104
        } else if (g_getenv("KDE_FULL_SESSION") && g_find_program_in_path("kfmclient")) {
105
            // We draaien KDE
106
            snprintf(cmd, sizeof(cmd), "kfmclient openURL %s", url);
107
        } else if (g_getenv("BROWSER")) {
108
            // XFCE4 gebruikt de environment variabele BROWSER om de standaardbrowser te bepalen.
109
            snprintf(cmd, sizeof(cmd), "%s %s", g_getenv("BROWSER"), url);
110
        } else {
111
            // Window manager onbekend..probeer firefox, mozilla of konqueror te lanceren indien aanwezig
112
            if (g_find_program_in_path("firefox")) {
113
                snprintf(cmd, sizeof(cmd), "firefox %s", url);
114
            } else if (g_find_program_in_path("mozilla")) {
115
                snprintf(cmd, sizeof(cmd), "mozilla %s", url);
116
            } else if (g_find_program_in_path("konqueror")) {
117
            snprintf(cmd, sizeof(cmd), "konqueror %s", url);
118
            } else {
119
                GtkWidget *windowMain = nntpgrab_gui_base_get_widget("windowMain");
120
                nntpgrab_gui_base_dialog_show(windowMain, _("Unable to find a external webbrowser"), GTK_MESSAGE_ERROR, GTK_BUTTONS_OK);
121
                g_free(url);
122
                return FALSE;
123
            }
124
        }
125

                
126
        g_spawn_command_line_async(cmd, &error);
127
        if (error) {
128
            g_print("%s: %s\n", error->message, cmd);
129
        }
130
#endif
131

                
132
        g_free(url);
133

                
134
        return FALSE;
135
    }
136

                
137
    return TRUE;
138
}
139

                
140
#ifndef HAVE_SOUP
141
static char *
142
retrieve_website_info(void)
143
{
144
    char            *ret;
145
    CURL            *curl = NULL;
146
    CURLcode         curl_retval;
147
    GString         *serverOutput;
148
    char            *user_agent;
149

                
150
    curl_global_init(CURL_GLOBAL_WIN32);
151

                
152
    curl = curl_easy_init();
153
    if(!curl) {
154
        g_warning(_("retrieve_website_info(): curl_easy_init() failed\n"));
155
        return NULL;
156
    }
157

                
158
    serverOutput = g_string_new("");
159

                
160
    curl_easy_setopt(curl, CURLOPT_URL, "https://www.nntpgrab.nl/info.php");
161
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_function);
162
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, serverOutput);
163
    curl_easy_setopt(curl, CURLOPT_NOPROGRESS, TRUE);
164
    curl_easy_setopt(curl, CURLOPT_ENCODING, "gzip");
165
    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 20);
166
    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, TRUE);
167
    //curl_easy_setopt(curl, CURLOPT_VERBOSE, TRUE);
168

                
169
    user_agent = g_strdup("NNTPGrab version " VERSION " (" OS ")");
170
    curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent);
171

                
172
#if 0 
173
    if (core->config->proxyHost && core->config->proxyPort) {
174
        curl_easy_setopt(curl, CURLOPT_PROXY, ftd_string_value(core->config->proxyHost));
175
        curl_easy_setopt(curl, CURLOPT_PROXYPORT, atoi(ftd_string_value(core->config->proxyPort)));
176

                
177
        if (core->config->proxyUser && core->config->proxyPassword) {
178
            int len = ftd_string_length(core->config->proxyUser) + ftd_string_length(core->config->proxyPassword) + 2;
179
            proxyUserPw = g_malloc (len);
180
            snprintf(proxyUserPw, len, "%s:%s", ftd_string_value(core->config->proxyUser), ftd_string_value(core->config->proxyPassword));
181
            curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, proxyUserPw);
182
        }
183
    }
184
#endif
185

                
186
    curl_retval = curl_easy_perform(curl);
187
    if (curl_retval == 0) {
188
        ret = g_strdup(serverOutput->str);
189
    } else {
190
        ret = NULL;
191
    }
192

                
193
    g_free(user_agent);
194
    g_string_free(serverOutput, TRUE);
195

                
196
    curl_easy_cleanup(curl);
197
    curl_global_cleanup();
198

                
199
    return ret;
200
}
201
#endif
202

                
203
typedef struct _changelog
204
{
205
    int major_version;
206
    int minor_version;
207
    int micro_version;
208
    GList *entries;
209
} ChangeLog;
210

                
211
typedef struct _update_info
212
{
213
    int stable_major_version;
214
    int stable_minor_version;
215
    int stable_micro_version;
216
    time_t date_latest_stable_version;
217
    char date_latest_stable_version_str[128];
218

                
219
    int unstable_major_version;
220
    int unstable_minor_version;
221
    int unstable_micro_version;
222
    time_t date_latest_unstable_version;
223
    char date_latest_unstable_version_str[128];
224

                
225
    GList *changelogs;
226
} UpdateInfo;
227

                
228
static char *xml_parser_errmsg = NULL;
229

                
230
static void
231
xml_parser_error_func (void *ctx, const char *msg, ...)
232
{
233
    va_list args;
234
    char *orig = xml_parser_errmsg;
235
    char *tmp;
236

                
237
    va_start(args, msg);
238

                
239
    tmp = g_strdup_vprintf(msg, args);
240
    if (orig) {
241
        xml_parser_errmsg = g_strdup_printf("%s %s", orig, tmp);
242
        g_free(orig);
243
        g_free(tmp);
244
    } else {
245
        xml_parser_errmsg = tmp;
246
    }
247

                
248
    va_end(args);
249
}
250

                
251
static void
252
parse_changelog(xmlDocPtr doc, xmlNodePtr cur, ChangeLog *changelog)
253
{
254
    gboolean translated_entry_seen = FALSE;
255
    const char * const *langs = g_get_language_names();
256
    int i = 0;
257
    const char *lang_requested = NULL;
258

                
259
    while(cur != NULL) {
260
        if (!xmlStrcmp(cur->name, (const xmlChar *) "VersionMajor")) {
261
            xmlChar *version = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
262
            changelog->major_version = atoi((const gchar *) version);
263
            xmlFree(version);
264
        }
265

                
266
        if (!xmlStrcmp(cur->name, (const xmlChar *) "VersionMinor")) {
267
            xmlChar *version = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
268
            changelog->minor_version = atoi((const gchar *) version);
269
            xmlFree(version);
270
        }
271

                
272
        if (!xmlStrcmp(cur->name, (const xmlChar *) "VersionMicro")) {
273
            xmlChar *version = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
274
            changelog->micro_version = atoi((const gchar *) version);
275
            xmlFree(version);
276
        }
277

                
278
        if (!translated_entry_seen && !xmlStrcmp(cur->name, (const xmlChar *) "Entry")) {
279
            xmlChar *entry = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
280
            changelog->entries = g_list_append(changelog->entries, g_strdup((const char *) entry));
281
            xmlFree(entry);
282
        }
283

                
284
        if (!xmlStrcmp(cur->name, (const xmlChar *) "Entry-i18n")) {
285
            xmlChar *prop = xmlNodeGetLang(cur);
286
            xmlChar *entry;
287
            if (!prop) {
288
                cur = cur->next;
289
                continue;
290
            }
291

                
292
            if (!lang_requested) {
293
                i = 0;
294

                
295
                while (langs[i]) {
296
                    if (!strcmp((const char *) prop, langs[i])) {
297
                        break;
298
                    }
299
                    i++;
300
                }
301

                
302
                /* Did we find a match? */
303
                if (langs[i]) {
304
                    lang_requested = langs[i];
305
                } else {
306
                    /* Nope, ignore the entry */
307
                    xmlFree(prop);
308
                    cur = cur->next;
309
                    continue;
310
                }
311
            } else if (strcmp((const char *) prop, lang_requested)) {
312
                /* Other language found, ignore */
313
                xmlFree(prop);
314
                cur = cur->next;
315
                continue;
316
            }
317

                
318
            translated_entry_seen = TRUE;
319

                
320
            entry = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
321
            changelog->entries = g_list_append(changelog->entries, g_strdup((const char *) entry));
322
            xmlFree(entry);
323
            xmlFree(prop);
324
        }
325

                
326
        cur = cur->next;
327
    }
328
}
329

                
330
static void
331
parse_changelogs(xmlDocPtr doc, xmlNodePtr cur, UpdateInfo *info)
332
{
333
    while(cur != NULL) {
334
        if (!xmlStrcmp(cur->name, (const xmlChar *) "Version")) {
335
            ChangeLog *changelog = g_slice_new0(ChangeLog);
336

                
337
            info->changelogs = g_list_append(info->changelogs, changelog);
338

                
339
            parse_changelog(doc, cur->xmlChildrenNode, changelog);
340
        }
341

                
342
        cur = cur->next;
343
    }
344
}
345

                
346
static void
347
parse_info(xmlDocPtr doc, xmlNodePtr cur, UpdateInfo *info)
348
{
349
    while(cur != NULL) {
350
        // stable
351
        if (!xmlStrcmp(cur->name, (const xmlChar *) "LatestStableMajor")) {
352
            xmlChar *version = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
353
            info->stable_major_version = atoi((const gchar *) version);
354
            xmlFree(version);
355
        }
356

                
357
        if (!xmlStrcmp(cur->name, (const xmlChar *) "LatestStableMinor")) {
358
            xmlChar *version = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
359
            info->stable_minor_version = atoi((const gchar *) version);
360
            xmlFree(version);
361
        }
362

                
363
        if (!xmlStrcmp(cur->name, (const xmlChar *) "LatestStableMicro")) {
364
            xmlChar *version = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
365
            info->stable_micro_version = atoi((const gchar *) version);
366
            xmlFree(version);
367
        }
368

                
369
        if (!xmlStrcmp(cur->name, (const xmlChar *) "DateLatestVersion")) {
370
            struct tm *now;
371

                
372
            xmlChar *version = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
373
            info->date_latest_stable_version = atoi((const gchar *) version);
374
            xmlFree(version);
375

                
376
            if ((now = localtime(&info->date_latest_stable_version)) != NULL) {
377
                strftime(info->date_latest_stable_version_str, sizeof(info->date_latest_stable_version_str), "%x", now);
378
            }
379
        }
380

                
381
        // unstable
382
        if (!xmlStrcmp(cur->name, (const xmlChar *) "LatestUnstableMajor")) {
383
            xmlChar *version = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
384
            info->unstable_major_version = atoi((const gchar *) version);
385
            xmlFree(version);
386
        }
387

                
388
        if (!xmlStrcmp(cur->name, (const xmlChar *) "LatestUnstableMinor")) {
389
            xmlChar *version = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
390
            info->unstable_minor_version = atoi((const gchar *) version);
391
            xmlFree(version);
392
        }
393

                
394
        if (!xmlStrcmp(cur->name, (const xmlChar *) "LatestUnstableMicro")) {
395
            xmlChar *version = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
396
            info->unstable_micro_version = atoi((const gchar *) version);
397
            xmlFree(version);
398
        }
399

                
400
        if (!xmlStrcmp(cur->name, (const xmlChar *) "DateLatestUnstable")) {
401
            struct tm *now;
402

                
403
            xmlChar *version = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
404
            info->date_latest_unstable_version = atoi((const gchar *) version);
405
            xmlFree(version);
406

                
407
            if ((now = localtime(&info->date_latest_unstable_version)) != NULL) {
408
                strftime(info->date_latest_unstable_version_str, sizeof(info->date_latest_unstable_version_str), "%x", now);
409
            }
410
        }
411
        cur = cur->next;
412
    }
413
}
414

                
415
static UpdateInfo *
416
parse_version_info(const char *contents, char **errmsg)
417
{
418
    xmlDocPtr    doc;
419
    xmlNodePtr   cur;
420
    UpdateInfo  *info = NULL;
421

                
422
    xmlSetGenericErrorFunc(NULL, xml_parser_error_func);
423

                
424
    if (xml_parser_errmsg) {
425
        g_free(xml_parser_errmsg);
426
        xml_parser_errmsg = NULL;
427
    }
428

                
429
    doc = xmlParseMemory(contents, strlen(contents));
430
    cur = xmlDocGetRootElement(doc);
431
    if (cur == NULL) {
432
        if (errmsg) {
433
            *errmsg = g_strdup(xml_parser_errmsg);
434
        }
435

                
436
        g_free(xml_parser_errmsg);
437
        xml_parser_errmsg = NULL;
438

                
439
        xmlFreeDoc(doc);
440
        return NULL;
441
    }
442

                
443
    if (xmlStrcmp(cur->name, (const xmlChar *) "NNTPGrab")) {
444
        if (errmsg) {
445
            *errmsg = g_strdup("parse_version_info: document of the wrong type, root node != NNTPGrab");
446
        }
447

                
448
        xmlFreeDoc(doc);
449

                
450
        return FALSE;
451
    }
452

                
453
    cur = cur->xmlChildrenNode;
454
    if (!cur) {
455
        if (errmsg) {
456
            *errmsg = g_strdup("parse_version_info: no children of root-node!");
457
        }
458

                
459
        xmlFreeDoc(doc);
460

                
461
        return FALSE;
462
    }
463

                
464
    info = g_slice_new0(UpdateInfo);
465

                
466
    do {
467
        if (!xmlStrcmp(cur->name, (const xmlChar *) "VersionInfo")) {
468
            parse_info(doc, cur->xmlChildrenNode, info);
469
        }
470

                
471
        if (!xmlStrcmp(cur->name, (const xmlChar *) "ChangeLog")) {
472
            parse_changelogs(doc, cur->xmlChildrenNode, info);
473
        }
474
    } while ((cur=cur->next));
475

                
476
    xmlFreeDoc(doc);
477

                
478
    return info;
479
}
480

                
481
static void
482
insert_link(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text)
483
{
484
    GtkTextTag *tag;
485

                
486
    tag = gtk_text_buffer_create_tag (buffer, NULL, "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE, "justification", GTK_JUSTIFY_CENTER, NULL);
487
    g_object_set_data (G_OBJECT (tag), "is_link", GINT_TO_POINTER (1));
488
    gtk_text_buffer_insert_with_tags (buffer, iter, text, -1, tag, NULL);
489
}
490

                
491
static void
492
follow_if_link(GtkWidget *text_view, GtkTextIter *iter)
493
{
494
    GSList *tags = NULL, *tagp = NULL;
495

                
496
    tags = gtk_text_iter_get_tags (iter);
497
    for (tagp = tags;  tagp != NULL;  tagp = tagp->next) {
498
        GtkTextTag *tag = tagp->data;
499
        gboolean is_link = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tag), "is_link"));
500

                
501
        if (is_link) {
502
            open_uri(NULL, "https://www.nntpgrab.nl", NULL);
503
            break;
504
        }
505
    }
506

                
507
    if (tags) {
508
        g_slist_free (tags);
509
    }
510
}
511

                
512
static gboolean
513
event_after(GtkWidget *text_view, GdkEvent  *ev)
514
{
515
    GtkTextIter start, end, iter;
516
    GtkTextBuffer *buffer;
517
    GdkEventButton *event;
518
    gint x, y;
519

                
520
    if (ev->type != GDK_BUTTON_RELEASE) {
521
        return FALSE;
522
    }
523

                
524
    event = (GdkEventButton *)ev;
525

                
526
    if (event->button != 1) {
527
        return FALSE;
528
    }
529

                
530
    buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
531

                
532
    /* we shouldn't follow a link if the user has selected something */
533
    gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
534
    if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end)) {
535
        return FALSE;
536
    }
537

                
538
    gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), GTK_TEXT_WINDOW_WIDGET, event->x, event->y, &x, &y);
539

                
540
    gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (text_view), &iter, x, y);
541

                
542
    follow_if_link (text_view, &iter);
543

                
544
    return FALSE;
545
}
546

                
547
static gboolean hovering_over_link = FALSE;
548
static GdkCursor *hand_cursor = NULL;
549
static GdkCursor *regular_cursor = NULL;
550

                
551
/* Looks at all tags covering the position (x, y) in the text view, 
552
 * and if one of them is a link, change the cursor to the "hands" cursor
553
 * typically used by web browsers.
554
 */
555
static void
556
set_cursor_if_appropriate(GtkTextView *text_view, gint x, gint y)
557
{
558
    GSList *tags = NULL, *tagp = NULL;
559
    GtkTextIter iter;
560
    gboolean hovering = FALSE;
561

                
562
    gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
563

                
564
    tags = gtk_text_iter_get_tags (&iter);
565
    for (tagp = tags;  tagp != NULL;  tagp = tagp->next) {
566
        GtkTextTag *tag = tagp->data;
567
        gboolean is_link = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tag), "is_link"));
568

                
569
        if (is_link != 0) {
570
            hovering = TRUE;
571
            break;
572
        }
573
    }
574

                
575
    if (hovering != hovering_over_link) {
576
        hovering_over_link = hovering;
577

                
578
        if (hovering_over_link) {
579
            gdk_window_set_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), hand_cursor);
580
        } else {
581
            gdk_window_set_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), regular_cursor);
582
        }
583
    }
584

                
585
    if (tags) {
586
        g_slist_free (tags);
587
    }
588
}
589

                
590
/* Update the cursor image if the pointer moved. 
591
 */
592
static gboolean
593
motion_notify_event (GtkWidget *text_view, GdkEventMotion *event)
594
{
595
    gint x, y;
596

                
597
    gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW (text_view), GTK_TEXT_WINDOW_WIDGET, event->x, event->y, &x, &y);
598

                
599
    set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), x, y);
600

                
601
#if GTK_CHECK_VERSION(2,14,0)
602
    gdk_window_get_pointer (gtk_widget_get_window(text_view), NULL, NULL, NULL);
603
#else
604
    gdk_window_get_pointer (text_view->window, NULL, NULL, NULL);
605
#endif
606

                
607
    return FALSE;
608
}
609

                
610
/* Also update the cursor image if the window becomes visible 
611
 * (e.g. when a window covering it got iconified).
612
 */
613
static gboolean
614
visibility_notify_event (GtkWidget *text_view, GdkEventVisibility *event)
615
{
616
    gint wx, wy, bx, by;
617

                
618
#if GTK_CHECK_VERSION(2,14,0)
619
    gdk_window_get_pointer (gtk_widget_get_window(text_view), &wx, &wy, NULL);
620
#else
621
    gdk_window_get_pointer (text_view->window, &wx, &wy, NULL);
622
#endif
623

                
624
    gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), GTK_TEXT_WINDOW_WIDGET, wx, wy, &bx, &by);
625

                
626
    set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), bx, by);
627

                
628
    return FALSE;
629
}
630

                
631
#define CHECK_VERSION_IS_OLDER(major,minor,micro)    \
632
    (MAJOR_VERSION < (major) || \
633
     (MAJOR_VERSION == (major) && MINOR_VERSION < (minor)) || \
634
     (MAJOR_VERSION == (major) && MINOR_VERSION == (minor) && \
635
      MICRO_VERSION < (micro)))
636

                
637
#define CHECK_IS_VERSION(major,minor,micro)    \
638
     (MAJOR_VERSION == (major) && MINOR_VERSION == (minor) && MICRO_VERSION == (micro))
639

                
640
static GtkWidget *
641
generate_version_table(UpdateInfo *info)
642
{
643
    GtkWidget *table;
644
    GtkWidget *label;
645
    char tmp[64];
646
    gboolean unstable_available;
647

                
648
    if (info->unstable_major_version > 0 ||
649
        info->unstable_minor_version > 0 ||
650
        info->unstable_micro_version > 0) {
651

                
652
        table = gtk_table_new(3, 2, FALSE);
653
        unstable_available = TRUE;
654
    } else {
655
        table = gtk_table_new(2, 2, FALSE);
656
        unstable_available = FALSE;
657
    }
658

                
659
    /* Active version */
660
    label = gtk_label_new(_("You are using version:"));
661
    gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, 0, 5, 1);
662

                
663
    memset(&tmp, 0, sizeof(tmp));
664
    snprintf(tmp, sizeof(tmp) - 1, "%i.%i.%i", MAJOR_VERSION, MINOR_VERSION, MICRO_VERSION);
665
    label = gtk_label_new(tmp);
666
    gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, 0, 5, 1);
667

                
668
    /* Latest available stable */
669
    if (unstable_available) {
670
        label = gtk_label_new(_("The latest available stable version is:"));
671
    } else {
672
        label = gtk_label_new(_("The latest available version is:"));
673
    }
674
    gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_EXPAND | GTK_FILL, 0, 5, 1);
675

                
676
    memset(&tmp, 0, sizeof(tmp));
677
    snprintf(tmp, sizeof(tmp) - 1, "%i.%i.%i", info->stable_major_version, info->stable_minor_version, info->stable_micro_version);
678
    label = gtk_label_new(tmp);
679
    gtk_table_attach(GTK_TABLE(table), label, 1, 2, 1, 2, GTK_EXPAND | GTK_FILL, 0, 5, 1);
680

                
681
    /* Latest available unstable */
682
    if (unstable_available) {
683
        label = gtk_label_new(_("The latest available unstable version is:"));
684
        gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3, GTK_EXPAND | GTK_FILL, 0, 5, 1);
685

                
686
        memset(&tmp, 0, sizeof(tmp));
687
        snprintf(tmp, sizeof(tmp) - 1, "%i.%i.%i", info->unstable_major_version, info->unstable_minor_version, info->unstable_micro_version);
688
        label = gtk_label_new(tmp);
689
        gtk_table_attach(GTK_TABLE(table), label, 1, 2, 2, 3, GTK_EXPAND | GTK_FILL, 0, 5, 1);
690
    }
691

                
692
    gtk_widget_show_all(table);
693

                
694
    return table;
695
}
696

                
697
#pragma GCC diagnostic push
698
#pragma GCC diagnostic ignored "-Wpointer-to-int-cast"
699
static void
700
generate_html(UpdateInfo *info)
701
{
702
    GtkWidget *textview;
703
    GtkTextBuffer *buffer;
704
    GtkTextIter iter;
705
    GtkTextIter iter2;
706
    GtkWidget *table;
707
    GtkTextChildAnchor *anchor;
708
    char *data;
709

                
710
    textview = nntpgrab_gui_base_get_widget("htmlInformation");
711
    buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
712

                
713
    gtk_text_buffer_get_end_iter(buffer, &iter);
714

                
715
    table = generate_version_table(info);
716
    anchor = gtk_text_buffer_create_child_anchor (buffer, &iter);
717
    gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(textview), table, anchor);
718
    iter2 = iter;
719
    gtk_text_iter_backward_char(&iter2);
720
    gtk_text_buffer_apply_tag_by_name(buffer, "center", &iter2, &iter);
721

                
722
    gtk_text_buffer_insert(buffer, &iter, "\n\n", -1);
723

                
724
    if (info->unstable_major_version < 0 && CHECK_IS_VERSION(info->stable_major_version, info->stable_minor_version, info->stable_micro_version)) {
725
        gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("You are using the latest version of NNTPGrab"), -1, "center", NULL);
726
        gtk_text_buffer_insert(buffer, &iter, "\n", -1);
727
        gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,  _("For more information, go to our website "), -1, "center", NULL);
728
        insert_link(buffer, &iter, "https://www.nntpgrab.nl");
729
    } else if (CHECK_IS_VERSION(info->unstable_major_version, info->unstable_minor_version, info->unstable_micro_version)) {
730
        data = g_strdup_printf("%s\n%s\n\n", _("You are using the latest unstable version of NNTPGrab"), _("If you find any bugs with this version, please report these"));
731
        gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, data, -1, "center", NULL);
732
        g_free(data);
733

                
734
        gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,  _("For more information, go to our website "), -1, "center", NULL);
735
        insert_link(buffer, &iter, "https://www.nntpgrab.nl");
736
    } else if ((info->unstable_major_version < 0 && !(CHECK_VERSION_IS_OLDER(info->stable_major_version, info->stable_minor_version, info->stable_micro_version))) ||
737
               (info->unstable_major_version >=0 && !(CHECK_VERSION_IS_OLDER(info->unstable_major_version, info->unstable_minor_version, info->unstable_micro_version)))) {
738

                
739
        data = g_strdup_printf("%s\n%s %"G_GUINT64_FORMAT" (%s %"G_GUINT64_FORMAT") %s %"G_GUINT64_FORMAT"\n\n", _("You are using a snapshot version of NNTPGrab"), _("This is subversion revision"), (guint64) GET_BUILD_SVN_REV, _("build"), (guint64) GET_BUILD_NUMBER, _("which was built on "), (guint64) GET_BUILD_DATE);
740
        gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, data, -1, "center", NULL);
741
        g_free(data);
742

                
743
        gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,  _("For more information, go to our website "), -1, "center", NULL);
744
        insert_link(buffer, &iter, "https://www.nntpgrab.nl");
745
    } else {
746
        GList *list;
747
        GList *list2;
748

                
749
        if (info->unstable_major_version >= 0) {
750
            gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("A new unstable version of NNTPGrab is available!"), -1, "center", "x-large", "bold", NULL);
751
            data = g_strdup_printf("\n\n%s %i.%i.%i %s %s %s\n\n", _("Version"), info->unstable_major_version, info->unstable_minor_version, info->unstable_micro_version, _("is released on"), info->date_latest_unstable_version_str, _("and contains the following changes :"));
752
        } else {
753
            gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("A new version of NNTPGrab is available!"), -1, "center", "x-large", "bold", NULL);
754
            data = g_strdup_printf("\n\n%s %i.%i.%i %s %s %s\n\n", _("Version"), info->stable_major_version, info->stable_minor_version, info->stable_micro_version, _("is released on"), info->date_latest_stable_version_str, _("and contains the following changes :"));
755
        }
756
        gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, data, -1, "center", NULL);
757
        g_free(data);
758

                
759
        list = info->changelogs;
760
        while (list) {
761
            ChangeLog *changelog = list->data;
762

                
763
            if (!CHECK_VERSION_IS_OLDER(changelog->major_version, changelog->minor_version, changelog->micro_version)) {
764
                list = g_list_next(list);
765
                continue;
766
            }
767

                
768
            data = g_strdup_printf("%s %i.%i.%i:\n\n", _("Changes in version"), changelog->major_version, changelog->minor_version, changelog->micro_version);
769
            gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, data, -1, "underline", "bold", NULL);
770
            g_free(data);
771

                
772
            list2 = changelog->entries;
773
            while (list2) {
774
                char *entry = list2->data;
775

                
776
                data = g_strdup_printf("• %s\n", entry);
777
                gtk_text_buffer_insert(buffer, &iter, data, -1);
778
                g_free(data);
779

                
780
                list2 = g_list_next(list2);
781
            }
782

                
783
            gtk_text_buffer_insert(buffer, &iter, "\n", -1);
784

                
785
            list = g_list_next(list);
786
        }
787
        gtk_text_buffer_insert(buffer, &iter, "\n", -1);
788

                
789
        if (info->unstable_major_version >= 0) {
790
            gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Please note that the unstable version may still contain bugs, so only use the unstable version when you want to help out testing"), -1, "center", "bold", NULL);
791
            gtk_text_buffer_insert(buffer, &iter, "\n\n", -1);
792
        }
793

                
794
        gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("To retrieve the latest version and for more information, go to our website "), -1, "center", NULL);
795
        insert_link(buffer, &iter, "https://www.nntpgrab.nl");
796
    }
797
}
798
#pragma GCC diagnostic pop
799

                
800
static gboolean
801
process_update_info(gpointer data)
802
{
803
    UpdateInfo *info;
804
    GList *list;
805
    char *errmsg = NULL;
806
    char *contents = (char*) data;
807

                
808
    if (!contents) {
809
        // Update check failed
810
        GtkWidget *windowMain = nntpgrab_gui_base_get_widget("windowMain");
811
        nntpgrab_gui_base_dialog_show(windowMain, _("Update check failed"), GTK_MESSAGE_ERROR, GTK_BUTTONS_OK);
812
        return FALSE;
813
    }
814

                
815
    info = parse_version_info(contents, &errmsg);
816

                
817
    g_free(contents);
818

                
819
    if (!info) {
820
        // Invalid data received
821
        char *msg;
822
        GtkWidget *windowMain = nntpgrab_gui_base_get_widget("windowMain");
823

                
824
        msg = g_strdup_printf(_("Update check failed due to invalid data:\n%s"), errmsg);
825
        nntpgrab_gui_base_dialog_show(windowMain, msg, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK);
826

                
827
        g_free(msg);
828
        g_free(errmsg);
829

                
830
        return FALSE;
831
    }
832

                
833
    generate_html(info);
834

                
835
    list = info->changelogs;
836
    while (list) {
837
        ChangeLog *changelog = list->data;
838
        GList *list2;
839

                
840
        list2 = changelog->entries;
841
        while (list2) {
842
            g_free(list2->data);
843
            list2 = g_list_next(list2);
844
        }
845
        g_list_free(changelog->entries);
846

                
847
        g_slice_free(ChangeLog, list->data);
848
        list = g_list_next(list);
849
    }
850
    g_list_free(info->changelogs);
851
    g_slice_free(UpdateInfo, info);
852

                
853
    return FALSE;
854
}
855

                
856
#ifdef HAVE_SOUP
857
static void
858
update_info_received(SoupSession *session, SoupMessage *msg, gpointer data)
859
{
860
    if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
861
        process_update_info(NULL);
862
        //g_object_unref(session);
863
        return;
864
    }
865

                
866
    process_update_info(g_strdup(msg->response_body->data));
867

                
868
    //g_object_unref(session);
869
}
870

                
871
#else
872
static gpointer
873
check_for_updates(gpointer data)
874
{
875
    char *contents;
876

                
877
    contents = retrieve_website_info();
878

                
879
    g_idle_add(process_update_info, contents);
880

                
881
    return NULL;
882
}
883
#endif
884

                
885
void
886
info_initialize()
887
{
888
    GtkWidget *textview;
889
    GtkTextBuffer *buffer;
890
    GtkTextIter iter;
891

                
892
    textview = nntpgrab_gui_base_get_widget("htmlInformation");
893
    buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
894

                
895
    gtk_text_buffer_create_tag (buffer, "extra_top_margin", "pixels-above-lines", 5, "pixels-above-lines-set", TRUE, NULL);
896
    gtk_text_buffer_create_tag (buffer, "underline", "underline", PANGO_UNDERLINE_SINGLE, "underline_set", TRUE, NULL);
897
    gtk_text_buffer_create_tag (buffer, "bold", "weight", PANGO_WEIGHT_BOLD, NULL);
898
    gtk_text_buffer_create_tag (buffer, "center", "justification", GTK_JUSTIFY_CENTER, NULL);
899
    gtk_text_buffer_create_tag (buffer, "x-large", "scale", PANGO_SCALE_X_LARGE, NULL);
900
    gtk_text_buffer_create_tag (buffer, "xx-large", "scale", PANGO_SCALE_XX_LARGE, NULL);
901

                
902
    gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);
903

                
904
    gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, _("Welcome to NNTPGrab"), -1, "extra_top_margin", "center", "xx-large", "bold", NULL);
905
    gtk_text_buffer_insert(buffer, &iter, "\n\n", -1);
906

                
907
    g_signal_connect (textview, "event-after", G_CALLBACK (event_after), NULL);
908
    g_signal_connect (textview, "motion-notify-event", G_CALLBACK (motion_notify_event), NULL);
909
    g_signal_connect (textview, "visibility-notify-event", G_CALLBACK (visibility_notify_event), NULL);
910

                
911
    hand_cursor = gdk_cursor_new (GDK_HAND2);
912
    regular_cursor = gdk_cursor_new (GDK_XTERM);
913

                
914
#ifndef HAVE_SOUP
915
    /* cURL doesn't support async transfers so we spawn a seperate thread for it */
916
    g_thread_create(check_for_updates, NULL, TRUE, NULL);
917
#else
918
    SoupSession *session = soup_session_sync_new();
919
    SoupMessage *msg;
920
    char *user_agent;
921

                
922
#if 0 
923
    SoupLogger *logger = soup_logger_new(SOUP_LOGGER_LOG_BODY, -1);
924
    soup_session_add_feature(session, SOUP_SESSION_FEATURE(logger));
925
    g_object_unref(logger);
926
#endif
927

                
928
#ifdef HAVE_SOUP_GNOME
929
    /* Enable proxy support when using GNOME 2.26.0 or higher */
930
    soup_session_add_feature_by_type(session, SOUP_TYPE_GNOME_FEATURES_2_26);
931
#endif
932

                
933
#ifdef HAVE_SOUP_GZIP
934
    /* Enable gzip compression */
935
    soup_session_add_feature_by_type(session, SOUP_TYPE_CONTENT_DECODER);
936
#endif
937

                
938
    msg = soup_message_new ("GET", "https://www.nntpgrab.nl/info.php");
939

                
940
    user_agent = g_strdup("NNTPGrab version " VERSION " (" OS ")");
941
    soup_message_headers_append(msg->request_headers, "User-Agent", user_agent);
942
    g_free(user_agent);
943

                
944
    soup_message_headers_append(msg->request_headers, "Keep-Alive", "30");
945
    soup_message_headers_append(msg->request_headers, "Connection", "Keep-alive");
946

                
947
    soup_session_queue_message(session, msg, update_info_received, NULL);
948
#endif
949
}
950

                
951
G_MODULE_EXPORT void
952
on_btnDonate_clicked(GtkWidget *caller, gpointer data)
953
{
954
    open_uri(NULL, "https://www.nntpgrab.nl/donate.php", NULL);
955
}