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