1 /*
2  *      updatechecker.c
3  *
4  *      Copyright 2011-2015 Frank Lanitz <frank(at)frank(dot)uvena(dot)de>
5  *
6  *      This program is free software; you can redistribute it and/or modify
7  *      it under the terms of the GNU General Public License as published by
8  *      the Free Software Foundation; either version 2 of the License, or
9  *      (at your option) any later version.
10  *
11  *      This program is distributed in the hope that it will be useful,
12  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *      GNU General Public License for more details.
15  *
16  *      You should have received a copy of the GNU General Public License
17  *      along with this program; if not, write to the Free Software
18  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20 
21 /* A little plugin for regular checks for updates of Geany */
22 
23 #include "libsoup/soup.h"
24 #include "stdlib.h"
25 
26 #ifdef HAVE_CONFIG_H
27 	#include "config.h" /* for the gettext domain */
28 #endif
29 
30 #include <geanyplugin.h>
31 
32 GeanyPlugin     *geany_plugin;
33 GeanyData       *geany_data;
34 
35 PLUGIN_VERSION_CHECK(224)
36 
37 PLUGIN_SET_TRANSLATABLE_INFO(
38     LOCALEDIR,
39     GETTEXT_PACKAGE,
40     _("Updatechecker"),
41     _("Checks whether there are updates for Geany available"),
42     VERSION,
43     "Frank Lanitz <frank@frank.uvena.de>")
44 
45 enum {
46     UPDATECHECK_MANUAL,
47     UPDATECHECK_STARTUP
48 };
49 
50 #define UPDATE_CHECK_URL "https://geany.org/service/version/"
51 
52 static GtkWidget *main_menu_item = NULL;
53 static void update_check_result_cb(SoupSession *session,
54     SoupMessage *msg, gpointer user_data);
55 
56 static gboolean check_on_startup = FALSE;
57 
58 /* Configuration file */
59 static gchar *config_file = NULL;
60 
61 
62 static struct
63 {
64     GtkWidget *run_on_startup;
65 }
66 config_widgets;
67 
68 typedef struct
69 {
70     gint major;
71     gint minor;
72     gint mini;
73     gchar *extra;
74 }
75 version_struct;
76 
77 
update_check(gint type)78 static void update_check(gint type)
79 {
80     SoupSession *soup;
81     SoupMessage *msg;
82     gchar *user_agent = g_strconcat("Updatechecker ", VERSION, " at Geany ",
83                                      GEANY_VERSION, NULL);
84 
85     g_message("Checking for updates (querying URL \"%s\")", UPDATE_CHECK_URL);
86     soup = soup_session_new_with_options(
87             SOUP_SESSION_USER_AGENT, user_agent,
88             SOUP_SESSION_TIMEOUT, 10,
89             NULL);
90 
91     g_free(user_agent);
92 
93     msg = soup_message_new ("GET", UPDATE_CHECK_URL);
94 
95     soup_session_queue_message (soup, msg, update_check_result_cb, GINT_TO_POINTER(type));
96 }
97 
98 
99 
100 static void
on_geany_startup_complete(G_GNUC_UNUSED GObject * obj,G_GNUC_UNUSED gpointer user_data)101 on_geany_startup_complete(G_GNUC_UNUSED GObject *obj,
102                           G_GNUC_UNUSED gpointer user_data)
103 {
104     if (check_on_startup == TRUE)
105     {
106         update_check(UPDATECHECK_STARTUP);
107     }
108 }
109 
110 
111 /* Based on some code by Sylpheed project.
112  * http://sylpheed.sraoss.jp/en/
113  * GPL FTW! */
parse_version_string(const gchar * ver,gint * major,gint * minor,gint * micro,gchar ** extra)114 static void parse_version_string(const gchar *ver, gint *major, gint *minor,
115                  gint *micro, gchar **extra)
116 {
117     gchar **vers;
118     vers = g_strsplit(ver, ".", 4);
119     if (vers[0])
120     {
121         *major = atoi(vers[0]);
122         if (vers[1])
123         {
124             *minor = atoi(vers[1]);
125             if (vers[2])
126             {
127                 *micro = atoi(vers[2]);
128                 if (vers[3])
129                 {
130                     *extra = g_strdup(vers[3]);
131                 }
132                 else
133                 {
134                     *extra = NULL;
135                 }
136             }
137             else
138             {
139                 *micro = 0;
140             }
141         }
142         else
143         {
144             *minor = 0;
145         }
146     }
147     else
148     {
149         *major = 0;
150     }
151     g_strfreev(vers);
152 }
153 
154 
155 /* Returns TRUE if the version installed is < as the version found
156  * on the server. All other cases a causes a FALSE. */
157 static gboolean
version_compare(const gchar * current_version)158 version_compare(const gchar *current_version)
159 {
160     version_struct geany_running;
161     version_struct geany_current;
162 
163     parse_version_string(GEANY_VERSION, &geany_running.major,
164         &geany_running.minor, &geany_running.mini, &geany_running.extra);
165 
166     parse_version_string(current_version, &geany_current.major,
167         &geany_current.minor, &geany_current.mini, &geany_current.extra);
168 
169     if ((geany_running.major < geany_current.major) ||
170         (geany_running.minor < geany_current.minor) ||
171         (geany_running.minor < geany_current.minor))
172     {
173         return TRUE;
174     }
175     else
176     {
177         return FALSE;
178     }
179 }
180 
181 
update_check_result_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)182 static void update_check_result_cb(SoupSession *session,
183     SoupMessage *msg, gpointer user_data)
184 {
185     gint type = GPOINTER_TO_INT(user_data);
186 
187     /* Checking whether we did get a valid (200) result */
188     if (msg->status_code == 200)
189     {
190         const gchar *remote_version = msg->response_body->data;
191         if (version_compare(remote_version) == TRUE)
192         {
193             gchar *update_msg = g_strdup_printf(
194                 _("There is a more recent version of Geany available: %s"),
195                 remote_version);
196             dialogs_show_msgbox(GTK_MESSAGE_INFO, "%s", update_msg);
197             g_message("%s", update_msg);
198             g_free(update_msg);
199         }
200         else
201         {
202             const gchar *no_update_msg = _("No newer Geany version available.");
203             if (type == UPDATECHECK_MANUAL)
204             {
205                 dialogs_show_msgbox(GTK_MESSAGE_INFO, "%s", no_update_msg);
206             }
207             else
208             {
209                 msgwin_status_add("%s", no_update_msg);
210             }
211             g_message("%s", no_update_msg);
212         }
213     }
214     else
215     {
216         gchar *error_message = g_strdup_printf(
217             _("Unable to perform version check.\nError code: %d \nError message: »%s«"),
218             msg->status_code, msg->reason_phrase);
219         if (type == UPDATECHECK_MANUAL)
220         {
221             dialogs_show_msgbox(GTK_MESSAGE_ERROR, "%s", error_message);
222         }
223         else
224         {
225             msgwin_status_add("%s", error_message);
226         }
227         g_warning("Connection error: Code: %d; Message: %s", msg->status_code, msg->reason_phrase);
228         g_free(error_message);
229     }
230 }
231 
manual_check_activated_cb(GtkMenuItem * menuitem,gpointer gdata)232 static void manual_check_activated_cb(GtkMenuItem *menuitem, gpointer gdata)
233 {
234     update_check(UPDATECHECK_MANUAL);
235 }
236 
237 
238 static void
on_configure_response(G_GNUC_UNUSED GtkDialog * dialog,gint response,G_GNUC_UNUSED gpointer user_data)239 on_configure_response(G_GNUC_UNUSED GtkDialog *dialog, gint response,
240                       G_GNUC_UNUSED gpointer user_data)
241 {
242     if (response == GTK_RESPONSE_OK || response == GTK_RESPONSE_APPLY)
243     {
244         GKeyFile *config = g_key_file_new();
245         gchar *data;
246         gchar *config_dir = g_path_get_dirname(config_file);
247 
248         /* Crabbing options that has been set */
249         check_on_startup =
250             gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(config_widgets.run_on_startup));
251 
252         /* write stuff to file */
253         g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL);
254 
255         g_key_file_set_boolean(config, "general", "check_for_updates_on_startup",
256             check_on_startup);
257 
258         if (!g_file_test(config_dir, G_FILE_TEST_IS_DIR)
259             && utils_mkdir(config_dir, TRUE) != 0)
260         {
261             dialogs_show_msgbox(GTK_MESSAGE_ERROR,
262                 _("Plugin configuration directory could not be created."));
263         }
264         else
265         {
266             /* write config to file */
267             data = g_key_file_to_data(config, NULL, NULL);
268             utils_write_file(config_file, data);
269             g_free(data);
270         }
271 
272         g_free(config_dir);
273         g_key_file_free(config);
274     }
275 }
276 
277 
278 GtkWidget *
plugin_configure(GtkDialog * dialog)279 plugin_configure(GtkDialog * dialog)
280 {
281     GtkWidget   *vbox;
282     vbox = gtk_vbox_new(FALSE, 6);
283 
284     config_widgets.run_on_startup = gtk_check_button_new_with_label(
285         _("Run updatecheck on startup"));
286 
287     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(config_widgets.run_on_startup),
288         check_on_startup);
289 
290     gtk_box_pack_start(GTK_BOX(vbox), config_widgets.run_on_startup, FALSE, FALSE, 2);
291     gtk_widget_show_all(vbox);
292     g_signal_connect(dialog, "response", G_CALLBACK(on_configure_response), NULL);
293     return vbox;
294 }
295 
296 
297 /* Registering of callbacks for Geany events */
298 PluginCallback plugin_callbacks[] =
299 {
300     { "geany-startup-complete", (GCallback) &on_geany_startup_complete, FALSE, NULL },
301     { NULL, NULL, FALSE, NULL }
302 };
303 
304 
init_configuration(void)305 static void init_configuration(void)
306 {
307     GKeyFile *config = g_key_file_new();
308 
309     /* loading configurations from file ...*/
310     config_file = g_strconcat(geany->app->configdir, G_DIR_SEPARATOR_S,
311     "plugins", G_DIR_SEPARATOR_S,
312     "updatechecker", G_DIR_SEPARATOR_S, "general.conf", NULL);
313 
314     /* ... and Initialising options from config file */
315     g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL);
316 
317     check_on_startup = utils_get_setting_boolean(config, "general",
318         "check_for_updates_on_startup", FALSE);
319 
320     g_key_file_free(config);
321 }
322 
323 
plugin_init(GeanyData * data)324 void plugin_init(GeanyData *data)
325 {
326     init_configuration();
327 
328     main_menu_item = gtk_menu_item_new_with_mnemonic(_("Check for Updates"));
329     gtk_widget_show(main_menu_item);
330     gtk_container_add(GTK_CONTAINER(geany->main_widgets->tools_menu),
331         main_menu_item);
332     g_signal_connect(main_menu_item, "activate",
333         G_CALLBACK(manual_check_activated_cb), NULL);
334 }
335 
plugin_cleanup(void)336 void plugin_cleanup(void)
337 {
338     gtk_widget_destroy(main_menu_item);
339     g_free(config_file);
340 }
341