1 /*
2 * This file is part of Siril, an astronomy image processor.
3 * Copyright (C) 2005-2011 Francois Meyer (dulle at free.fr)
4 * Copyright (C) 2012-2021 team free-astro (see more in AUTHORS file)
5 * Reference site is https://free-astro.org/index.php/Siril
6 *
7 * Siril is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * Siril is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with Siril. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 #include <json-glib/json-glib.h>
25
26 #ifdef HAVE_LIBCURL
27 #include <curl/curl.h>
28 #endif
29
30 #include <string.h>
31
32 #include "core/siril.h"
33 #include "core/proto.h"
34 #include "core/processing.h"
35 #include "gui/utils.h"
36 #include "gui/message_dialog.h"
37 #include "gui/progress_and_log.h"
38 #include "core/siril_update.h"
39
40
41 #define DOMAIN "https://siril.org/"
42 #define SIRIL_VERSIONS DOMAIN"siril_versions.json"
43 #define SIRIL_DOWNLOAD DOMAIN"download"
44 #define GITLAB_URL "https://gitlab.com/free-astro/siril/raw"
45
46 static gchar *get_changelog(gint x, gint y, gint z, gint p);
47
48
49 // taken from gimp
siril_update_get_highest(JsonParser * parser,gchar ** highest_version,gint64 * release_timestamp,gint * build_revision,gchar ** build_comment)50 static gboolean siril_update_get_highest(JsonParser *parser,
51 gchar **highest_version, gint64 *release_timestamp,
52 gint *build_revision, gchar **build_comment) {
53 JsonPath *path;
54 JsonNode *result;
55 JsonArray *versions;
56 const gchar *platform;
57 const gchar *path_str;
58 const gchar *release_date = NULL;
59 GError *error = NULL;
60 gint i;
61
62 g_return_val_if_fail(highest_version != NULL, FALSE);
63 g_return_val_if_fail(release_timestamp != NULL, FALSE);
64 g_return_val_if_fail(build_revision != NULL, FALSE);
65 g_return_val_if_fail(build_comment != NULL, FALSE);
66
67 *highest_version = NULL;
68 *release_timestamp = 0;
69 *build_revision = 0;
70 *build_comment = NULL;
71
72 path_str = "$['RELEASE'][*]";
73
74 /* For Windows and macOS, let's look if installers are available.
75 * For other platforms, let's just look for source release.
76 */
77 if (g_strcmp0(SIRIL_BUILD_PLATFORM_FAMILY, "windows") == 0
78 || g_strcmp0(SIRIL_BUILD_PLATFORM_FAMILY, "macos") == 0)
79 platform = SIRIL_BUILD_PLATFORM_FAMILY;
80 else
81 platform = "source";
82
83 path = json_path_new();
84 /* Ideally we could just use Json path filters like this to
85 * retrieve only released binaries for a given platform:
86 * g_strdup_printf ("$['STABLE'][?(@.%s)]['version']", platform);
87 * json_array_get_string_element (result, 0);
88 * And that would be it! We'd have our last release for given
89 * platform.
90 * Unfortunately json-glib does not support filter syntax, so we
91 * end up looping through releases.
92 */
93 if (!json_path_compile(path, path_str, &error)) {
94 g_warning("%s: path compilation failed: %s\n", G_STRFUNC,
95 error->message);
96 g_clear_error(&error);
97 g_object_unref(path);
98
99 return FALSE;
100 }
101 result = json_path_match(path, json_parser_get_root(parser));
102 if (!JSON_NODE_HOLDS_ARRAY(result)) {
103 g_printerr("%s: match for \"%s\" is not a JSON array.\n",
104 G_STRFUNC, path_str);
105 g_object_unref(path);
106
107 return FALSE;
108 }
109
110 versions = json_node_get_array(result);
111 for (i = 0; i < (gint) json_array_get_length(versions); i++) {
112 JsonObject *version;
113
114 /* Note that we don't actually look for the highest version,
115 * but for the highest version for which a build for your
116 * platform (and optional build-id) is available.
117 *
118 * So we loop through the version list then the build array
119 * and break at first compatible release, since JSON arrays
120 * are ordered.
121 */
122 version = json_array_get_object_element(versions, i);
123 if (json_object_has_member(version, platform)) {
124 JsonArray *builds;
125 gint j;
126
127 builds = json_object_get_array_member(version, platform);
128
129 for (j = 0; j < (gint) json_array_get_length(builds); j++) {
130 const gchar *build_id = NULL;
131 JsonObject *build;
132
133 build = json_array_get_object_element(builds, j);
134 if (json_object_has_member(build, "build-id"))
135 build_id = json_object_get_string_member (build, "build-id");
136 if (g_strcmp0(build_id, "org.free_astro.siril") == 0
137 || g_strcmp0(platform, "source") == 0) {
138 /* Release date is the build date if any set,
139 * otherwise the main version release date.
140 */
141 if (json_object_has_member(build, "date"))
142 release_date = json_object_get_string_member(build, "date");
143 else
144 release_date = json_object_get_string_member(version, "date");
145
146 /* These are optional data. */
147 if (json_object_has_member(build, "revision"))
148 *build_revision = json_object_get_int_member(build, "revision");
149 if (json_object_has_member(build, "comment"))
150 *build_comment = g_strdup(json_object_get_string_member(build, "comment"));
151 break;
152 }
153 }
154
155 if (release_date) {
156 *highest_version = g_strdup(json_object_get_string_member(version, "version"));
157 break;
158 }
159 }
160 }
161
162 if (*highest_version && *release_date) {
163 GDateTime *datetime;
164 gchar *str;
165
166 str = g_strdup_printf("%s 00:00:00Z", release_date);
167 datetime = g_date_time_new_from_iso8601(str, NULL);
168 g_free(str);
169
170 if (datetime) {
171 *release_timestamp = g_date_time_to_unix(datetime);
172 g_date_time_unref(datetime);
173 } else {
174 /* JSON file data bug. */
175 g_printerr("%s: release date for version %s not properly formatted: %s\n",
176 G_STRFUNC, *highest_version, release_date);
177
178 g_clear_pointer(highest_version, g_free);
179 g_clear_pointer(build_comment, g_free);
180 *build_revision = 0;
181 }
182 }
183
184 json_node_unref(result);
185 g_object_unref(path);
186
187 return (*highest_version != NULL);
188 }
189
190 /**
191 * Check if the version is a patched version.
192 * patched version are named like that x.y.z.patch where patch only contains digits.
193 * if patch contains alpha char it is because that's an alpha or beta version. Not a patched one.
194 * @param version version to be tested
195 * @return 0 if the version is not patched. The version of the patch is returned otherwise.
196 */
check_for_patch(gchar * version)197 static guint check_for_patch(gchar *version) {
198 guint i = 0;
199
200 while (version[i]) {
201 if (g_ascii_isalpha(version[i])) return 0;
202 i++;
203 }
204 return (g_ascii_strtoull(version, NULL, 10));
205 }
206
get_current_version_number()207 static version_number get_current_version_number() {
208 gchar **fullVersionNumber;
209 version_number version;
210
211 fullVersionNumber = g_strsplit_set(PACKAGE_VERSION, ".-", -1);
212 version.major_version = g_ascii_strtoull(fullVersionNumber[0], NULL, 10);
213 version.minor_version = g_ascii_strtoull(fullVersionNumber[1], NULL, 10);
214 version.micro_version = g_ascii_strtoull(fullVersionNumber[2], NULL, 10);
215 version.patched_version = (fullVersionNumber[3] == NULL) ? 0 : check_for_patch(fullVersionNumber[3]);
216
217 g_strfreev(fullVersionNumber);
218
219 return version;
220 }
221
get_last_version_number(gchar * version_str)222 static version_number get_last_version_number(gchar *version_str) {
223 gchar **v;
224 version_number version = { 0 };
225
226 v = g_strsplit_set(version_str, ".-", -1);
227
228 if (v[0])
229 version.major_version = g_ascii_strtoull(v[0], NULL, 10);
230 if (v[0] && v[1])
231 version.minor_version = g_ascii_strtoull(v[1], NULL, 10);
232 if (v[0] && v[1] && v[2])
233 version.micro_version = g_ascii_strtoull(v[2], NULL, 10);
234 if (v[0] && v[1] && v[2] && v[3])
235 version.patched_version = g_ascii_strtoull(v[3], NULL, 10);
236
237 g_strfreev(v);
238 return version;
239 }
240
241 /**
242 * This function compare x1.y1.z1.patch1 vs x2.y2.z2.patch2
243 * @param v1 First version number to be tested
244 * @param v2 Second version number to be tested
245 * @return -1 if v1 < v2, 1 if v1 > v2 and 0 if v1 is equal to v2
246 */
compare_version(version_number v1,version_number v2)247 static int compare_version(version_number v1, version_number v2) {
248 if (v1.major_version < v2.major_version)
249 return -1;
250 else if (v1.major_version > v2.major_version)
251 return 1;
252 else {
253 if (v1.minor_version < v2.minor_version)
254 return -1;
255 else if (v1.minor_version > v2.minor_version)
256 return 1;
257 else {
258 if (v1.micro_version < v2.micro_version)
259 return -1;
260 else if (v1.micro_version > v2.micro_version)
261 return 1;
262 else {
263 if (v1.patched_version < v2.patched_version)
264 return -1;
265 else if (v1.patched_version > v2.patched_version)
266 return 1;
267 }
268 }
269 }
270 return 0;
271 }
272
parse_changelog(gchar * changelog)273 static gchar *parse_changelog(gchar *changelog) {
274 gchar **token;
275 GString *strResult;
276 guint nargs, i;
277
278 token = g_strsplit(changelog, "\n", -1);
279 nargs = g_strv_length(token);
280
281 strResult = g_string_new(token[0]);
282 strResult = g_string_append(strResult, "\n\n");
283 /* we start at line 3 */
284 i = 3;
285 while (i < nargs && token[i][0] != '\0') {
286 strResult = g_string_append(strResult, token[i]);
287 strResult = g_string_append(strResult, "\n");
288 i++;
289 }
290 g_strfreev(token);
291 return g_string_free(strResult, FALSE);
292 }
293
check_version(gchar * version,gboolean * verbose,gchar ** data)294 static gchar *check_version(gchar *version, gboolean *verbose, gchar **data) {
295 gchar *changelog = NULL;
296 gchar *msg = NULL;
297
298 version_number last_version_available = get_last_version_number(version);
299 version_number current_version = get_current_version_number();
300 guint x = last_version_available.major_version;
301 guint y = last_version_available.minor_version;
302 guint z = last_version_available.micro_version;
303 guint patch = last_version_available.patched_version;
304 if (x == 0 && y == 0 && z == 0) {
305 if (*verbose)
306 msg = siril_log_message(_("No update check: cannot fetch version file\n"));
307 } else {
308 if (compare_version(current_version, last_version_available) < 0) {
309 msg = siril_log_message(_("New version is available. You can download it at "
310 "<a href=\"%s\">%s</a>\n"),
311 SIRIL_DOWNLOAD, SIRIL_DOWNLOAD);
312 changelog = get_changelog(x, y, z, patch);
313 if (changelog) {
314 *data = parse_changelog(changelog);
315 /* force the verbose variable */
316 *verbose = TRUE;
317 }
318 } else if (compare_version(current_version, last_version_available) > 0) {
319 if (*verbose)
320 msg = siril_log_message(_("No update check: this is a development version\n"));
321 } else {
322 if (*verbose)
323 msg = siril_log_message(_("Siril is up to date\n"));
324 }
325 g_free(changelog);
326 }
327 return msg;
328 }
329
330 // TODO: For now, to fix this bug https://gitlab.com/free-astro/siril/-/issues/604() we need to use GIO for Windows
331 #if defined HAVE_LIBCURL && !defined _WIN32
332
333 struct _update_data {
334 gchar *url;
335 long code;
336 gchar *content;
337 gboolean verbose;
338 };
339
340
341 static const int DEFAULT_FETCH_RETRIES = 5;
342 static CURL *curl;
343
344 struct ucontent {
345 gchar *data;
346 size_t len;
347 };
348
cbk_curl(void * buffer,size_t size,size_t nmemb,void * userp)349 static size_t cbk_curl(void *buffer, size_t size, size_t nmemb, void *userp) {
350 size_t realsize = size * nmemb;
351 struct ucontent *mem = (struct ucontent *) userp;
352
353 mem->data = g_try_realloc(mem->data, mem->len + realsize + 1);
354
355 memcpy(&(mem->data[mem->len]), buffer, realsize);
356 mem->len += realsize;
357 mem->data[mem->len] = 0;
358
359 return realsize;
360 }
361
init()362 static void init() {
363 if (!curl) {
364 g_fprintf(stdout, "initializing CURL\n");
365 curl_global_init(CURL_GLOBAL_ALL);
366 curl = curl_easy_init();
367 }
368
369 if (!curl)
370 exit(EXIT_FAILURE);
371 }
372
http_cleanup()373 static void http_cleanup() {
374 curl_easy_cleanup(curl);
375 curl_global_cleanup();
376 curl = NULL;
377 }
378
get_changelog(gint x,gint y,gint z,gint p)379 static gchar *get_changelog(gint x, gint y, gint z, gint p) {
380 struct ucontent *changelog;
381 gchar *result = NULL;
382 gchar str[20];
383 gchar *changelog_url;
384 long code;
385
386 GString *url = g_string_new(GITLAB_URL);
387
388 changelog = g_try_malloc(sizeof(struct ucontent));
389 if (changelog == NULL) {
390 PRINT_ALLOC_ERR;
391 return NULL;
392 }
393
394 changelog->data = g_malloc(1);
395 changelog->data[0] = '\0';
396 changelog->len = 0;
397
398 if (p != 0) {
399 g_snprintf(str, sizeof(str), "/%d.%d.%d.%d/", x, y, z, p);
400 } else {
401 g_snprintf(str, sizeof(str), "/%d.%d.%d/", x, y, z);
402 }
403 url = g_string_append(url, str);
404 url = g_string_append(url, "ChangeLog");
405
406 changelog_url = g_string_free(url, FALSE);
407 curl_easy_setopt(curl, CURLOPT_URL, changelog_url);
408 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
409 curl_easy_setopt(curl, CURLOPT_WRITEDATA, changelog);
410 if (curl_easy_perform(curl) == CURLE_OK) {
411 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
412 if (code == 200) {
413 result = g_strdup(changelog->data);
414 }
415 }
416 if (!result)
417 g_free(changelog->data);
418 g_free(changelog);
419 g_free(changelog_url);
420
421 return result;
422 }
423
check_update_version(struct _update_data * args)424 static gchar *check_update_version(struct _update_data *args) {
425 JsonParser *parser;
426 gchar *last_version = NULL;
427 gchar *build_comment = NULL;
428 gint64 release_timestamp = 0;
429 gint build_revision = 0;
430 GError *error = NULL;
431 gchar *msg = NULL;
432 gchar *data = NULL;
433 GtkMessageType message_type = GTK_MESSAGE_ERROR;
434
435 parser = json_parser_new();
436 if (!json_parser_load_from_data(parser, args->content, -1, &error)) {
437 g_printerr("%s: parsing of %s failed: %s\n", G_STRFUNC,
438 args->url, error->message);
439 g_clear_object(&parser);
440 g_clear_error(&error);
441
442 return NULL;
443 }
444
445 siril_update_get_highest(parser, &last_version, &release_timestamp, &build_revision, &build_comment);
446
447 if (last_version) {
448 g_fprintf(stdout, "Last available version: %s\n", last_version);
449
450 msg = check_version(last_version, &(args->verbose), &data);
451 message_type = GTK_MESSAGE_INFO;
452 } else {
453 msg = siril_log_message(_("Cannot fetch version file\n"));
454 }
455
456 if (args->verbose) {
457 set_cursor_waiting(FALSE);
458 if (msg) {
459 siril_data_dialog(message_type, _("Software Update"), msg, data);
460 }
461 }
462
463 g_clear_pointer(&last_version, g_free);
464 g_clear_pointer(&build_comment, g_free);
465 g_object_unref(parser);
466
467 return msg;
468 }
469
end_update_idle(gpointer p)470 static gboolean end_update_idle(gpointer p) {
471 char *msg = NULL;
472 struct _update_data *args = (struct _update_data *) p;
473
474 if (args->content == NULL) {
475 switch(args->code) {
476 case 0:
477 msg = siril_log_message(_("Unable to check updates! "
478 "Please Check your network connection\n"));
479 break;
480 default:
481 msg = siril_log_message(_("Unable to check updates! Error: %ld\n"),
482 args->code);
483 }
484 } else {
485 msg = check_update_version(args);
486 }
487
488 /* free data */
489 g_free(args->content);
490 free(args);
491 http_cleanup();
492 set_progress_bar_data(PROGRESS_TEXT_RESET, PROGRESS_RESET);
493 stop_processing_thread();
494 return FALSE;
495 }
496
fetch_url(gpointer p)497 static gpointer fetch_url(gpointer p) {
498 struct ucontent *content;
499 gchar *result;
500 long code = -1L;
501 int retries;
502 unsigned int s;
503 struct _update_data *args = (struct _update_data *) p;
504
505 content = g_try_malloc(sizeof(struct ucontent));
506 if (content == NULL) {
507 PRINT_ALLOC_ERR;
508 return NULL;
509 }
510
511 g_fprintf(stdout, "fetch_url(): %s\n", args->url);
512
513 init();
514 set_progress_bar_data(NULL, 0.1);
515
516 result = NULL;
517
518 retries = DEFAULT_FETCH_RETRIES;
519
520 retrieve: content->data = g_malloc(1);
521 content->data[0] = '\0';
522 content->len = 0;
523
524 curl_easy_setopt(curl, CURLOPT_URL, args->url);
525 curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L);
526 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cbk_curl);
527 curl_easy_setopt(curl, CURLOPT_WRITEDATA, content);
528 curl_easy_setopt(curl, CURLOPT_USERAGENT, "siril/0.0");
529 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
530
531 CURLcode retval = curl_easy_perform(curl);
532 if (retval == CURLE_OK) {
533 if (retries == DEFAULT_FETCH_RETRIES) set_progress_bar_data(NULL, 0.4);
534 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
535 if (retries == DEFAULT_FETCH_RETRIES) set_progress_bar_data(NULL, 0.6);
536
537 switch (code) {
538 case 200:
539 result = content->data;
540 break;
541 case 500:
542 case 502:
543 case 503:
544 case 504:
545 g_fprintf(stderr, "Fetch failed with code %ld for URL %s\n", code,
546 args->url);
547
548 if (retries && get_thread_run()) {
549 double progress = (DEFAULT_FETCH_RETRIES - retries) / (double) DEFAULT_FETCH_RETRIES;
550 progress *= 0.4;
551 progress += 0.6;
552 s = 2 * (DEFAULT_FETCH_RETRIES - retries) + 2;
553 char *msg = siril_log_message(_("Error: %ld. Wait %us before retry\n"), code, s);
554 msg[strlen(msg) - 1] = 0; /* we remove '\n' at the end */
555 set_progress_bar_data(msg, progress);
556 g_usleep(s * 1E6);
557
558 g_free(content->data);
559 retries--;
560 goto retrieve;
561 }
562
563 break;
564 default:
565 g_fprintf(stderr, "Fetch failed with code %ld for URL %s\n", code,
566 args->url);
567 }
568 g_fprintf(stderr, "Fetch succeeded with code %ld for URL %s\n", code,
569 args->url);
570 args->code = code;
571 } else {
572 siril_log_color_message(_("Cannot retrieve information from the update URL. Error: [%ld]\n"), "red", retval);
573 }
574 set_progress_bar_data(NULL, PROGRESS_DONE);
575
576 if (!result)
577 g_free(content->data);
578 g_free(content);
579
580 args->content = result;
581
582 gdk_threads_add_idle(end_update_idle, args);
583 return NULL;
584 }
585
siril_check_updates(gboolean verbose)586 void siril_check_updates(gboolean verbose) {
587 struct _update_data *args;
588
589 args = malloc(sizeof(struct _update_data));
590 args->url = SIRIL_VERSIONS;
591 args->code = 0L;
592 args->content = NULL;
593 args->verbose = verbose;
594
595 set_progress_bar_data(_("Looking for updates..."), PROGRESS_NONE);
596 if (args->verbose)
597 set_cursor_waiting(TRUE);
598 start_in_new_thread(fetch_url, args);
599 }
600
601 #else
602
get_changelog(gint x,gint y,gint z,gint p)603 static gchar *get_changelog(gint x, gint y, gint z, gint p) {
604 GError *error = NULL;
605 gchar *result = NULL;
606 gchar *str;
607
608 if (p != 0) {
609 str = g_strdup_printf("/%d.%d.%d.%d/", x, y, z, p);
610 } else {
611 str = g_strdup_printf("/%d.%d.%d/", x, y, z);
612 }
613 GString *url = g_string_new(GITLAB_URL);
614 url = g_string_append(url, str);
615 url = g_string_append(url, "ChangeLog");
616
617 gchar *changelog_url = g_string_free(url, FALSE);
618 GFile *file = g_file_new_for_uri(changelog_url);
619
620 if (!g_file_load_contents(file, NULL, &result, NULL, NULL, &error)) {
621 siril_log_message(_("Error loading url: %s: %s\n"), changelog_url, error->message);
622 g_clear_error(&error);
623 }
624
625 g_free(changelog_url);
626 g_free(str);
627 g_object_unref(file);
628
629 return result;
630 }
631
siril_check_updates_callback(GObject * source,GAsyncResult * result,gpointer user_data)632 static void siril_check_updates_callback(GObject *source, GAsyncResult *result,
633 gpointer user_data) {
634 gboolean verbose = GPOINTER_TO_INT(user_data);
635 char *file_contents = NULL;
636 gsize file_length = 0;
637 GError *error = NULL;
638 gchar *msg = NULL;
639 gchar *data = NULL;
640 GtkMessageType message_type = GTK_MESSAGE_ERROR;
641
642 if (g_file_load_contents_finish(G_FILE(source), result, &file_contents,
643 &file_length,
644 NULL, &error)) {
645 JsonParser *parser;
646 gchar *last_version = NULL;
647 gchar *build_comment = NULL;
648 gint64 release_timestamp = 0;
649 gint build_revision = 0;
650
651 parser = json_parser_new();
652 if (!json_parser_load_from_data(parser, file_contents, file_length,
653 &error)) {
654 g_printerr("%s: parsing of %s failed: %s\n", G_STRFUNC,
655 g_file_get_uri(G_FILE(source)), error->message);
656 g_free(file_contents);
657 g_clear_object(&parser);
658 g_clear_error(&error);
659
660 return;
661 }
662
663 siril_update_get_highest(parser, &last_version, &release_timestamp, &build_revision, &build_comment);
664
665 if (last_version) {
666 g_fprintf(stdout, "Last available version: %s\n", last_version);
667
668 msg = check_version(last_version, &verbose, &data);
669 message_type = GTK_MESSAGE_INFO;
670 } else {
671 msg = siril_log_message(_("Cannot fetch version file\n"));
672 }
673
674 g_clear_pointer(&last_version, g_free);
675 g_clear_pointer(&build_comment, g_free);
676 g_object_unref(parser);
677 g_free(file_contents);
678 } else {
679 g_printerr("%s: loading of %s failed: %s\n", G_STRFUNC,
680 g_file_get_uri(G_FILE(source)), error->message);
681 g_clear_error(&error);
682 msg = siril_log_message(_("Cannot fetch version file\n"));
683 }
684 if (verbose) {
685 set_cursor_waiting(FALSE);
686 if (msg) {
687 siril_data_dialog(message_type, _("Software Update"), msg, data);
688 }
689 }
690 /* free data */
691 g_free(data);
692 }
693
siril_check_updates(gboolean verbose)694 void siril_check_updates(gboolean verbose) {
695 GFile *siril_versions;
696
697 siril_versions = g_file_new_for_uri(SIRIL_VERSIONS);
698
699 g_file_load_contents_async(siril_versions, NULL, siril_check_updates_callback, GINT_TO_POINTER(verbose));
700 g_object_unref(siril_versions);
701 }
702 #endif
703