Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
updates.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#include "config.h" // DT_BUILD_CHANNEL, darktable_commit_hash, darktable_package_version
20
21#include "common/updates.h"
22
23#include "common/conf.h"
24#include "common/logging.h"
25
26#include <curl/curl.h>
27#include <json-glib/json-glib.h>
28#include <string.h>
29#include <time.h>
30
31#define DT_UPDATES_ENABLED_KEY "updates/enabled"
32#define DT_UPDATES_LAST_CHECK_KEY "updates/last_check"
33#define DT_UPDATES_MANIFEST_KEY "updates/manifest_url"
34#define DT_UPDATES_INTERVAL (24 * 60 * 60)
35// A manifest is a few kilobytes; anything past this is not the manifest.
36#define DT_UPDATES_MAX_BODY (1 << 20)
37
38static GThread *_worker = NULL;
39static gint _shutting_down = 0;
41static char *_download_url = NULL;
42static char *_available_version = NULL;
43
45{
46 // The AppImage runtime exports the path of the image it mounted.
47 if(g_getenv("APPIMAGE")) return "appimage";
48 // Every Flatpak sandbox carries its metadata at the root.
49 if(g_file_test("/.flatpak-info", G_FILE_TEST_EXISTS)) return "flatpak";
50#if defined(__APPLE__)
51#if defined(__aarch64__) || defined(__arm64__)
52 return "dmg-arm64";
53#else
54 return "dmg-i386";
55#endif
56#elif defined(_WIN32)
57 return "exe";
58#else
59 return NULL;
60#endif
61}
62
64{
65 return _download_url;
66}
67
69{
70 return _available_version;
71}
72
73static size_t _write_cb(char *data, size_t size, size_t nmemb, void *user)
74{
75 GString *body = (GString *)user;
76 const size_t n = size * nmemb;
77 if(body->len + n > DT_UPDATES_MAX_BODY) return 0; // aborts the transfer
78 g_string_append_len(body, data, n);
79 return n;
80}
81
82typedef struct _found_t
83{
84 char *url;
85 char *version;
87
88// GUI thread: publish the result and tell the user once.
89static gboolean _announce(gpointer data)
90{
91 _found_t *found = (_found_t *)data;
92 if(!g_atomic_int_get(&_shutting_down))
93 {
94 g_free(_download_url);
95 g_free(_available_version);
96 _download_url = found->url;
98 found->url = found->version = NULL;
100 }
101 g_free(found->url);
102 g_free(found->version);
103 g_free(found);
104 return G_SOURCE_REMOVE;
105}
106
107// Parse the manifest and decide. Returns a heap _found_t when a newer build exists.
108static _found_t *_evaluate(const char *body, gsize len)
109{
110 JsonParser *parser = json_parser_new();
111 _found_t *found = NULL;
112 if(json_parser_load_from_data(parser, body, len, NULL))
113 {
114 JsonNode *root = json_parser_get_root(parser);
115 JsonObject *top = JSON_NODE_HOLDS_OBJECT(root) ? json_node_get_object(root) : NULL;
116 JsonObject *formats = (top && json_object_has_member(top, "formats")) ? json_object_get_object_member(top, "formats") : NULL;
117 const char *format = dt_updates_runtime_format();
118 JsonObject *entry = (formats && format && json_object_has_member(formats, format))
119 ? json_object_get_object_member(formats, format)
120 : NULL;
121 if(entry)
122 {
123 const char *commit = json_object_has_member(entry, "commit") ? json_object_get_string_member(entry, "commit") : NULL;
124 const char *version = json_object_has_member(entry, "version") ? json_object_get_string_member(entry, "version") : NULL;
125 const char *url = json_object_has_member(entry, "url") ? json_object_get_string_member(entry, "url") : NULL;
126 // The nightly channel is monotonic: a build whose commit is not ours is newer.
127 // The manifest carries the full SHA and so does darktable_commit_hash; a short
128 // hash in the manifest (commit resolution failed upstream) is compared as a
129 // prefix rather than ignored.
130 const gboolean differs = commit && *commit && strncmp(commit, darktable_commit_hash, strlen(commit)) != 0;
131 dt_print(DT_DEBUG_CONTROL, "[updates] running %s %.10s, manifest %s %.10s -> %s\n", format,
132 darktable_commit_hash, version ? version : "?", commit ? commit : "?",
133 differs ? "newer available" : "up to date");
134 if(differs && url)
135 {
136 found = g_new0(_found_t, 1);
137 found->url = g_strdup(url);
138 found->version = g_strdup(version ? version : "unknown");
139 }
140 }
141 else
142 dt_print(DT_DEBUG_CONTROL, "[updates] manifest has no entry for format '%s'\n", format ? format : "(unknown)");
143 }
144 else
145 dt_print(DT_DEBUG_CONTROL, "[updates] manifest is not valid JSON\n");
146 g_object_unref(parser);
147 return found;
148}
149
150static gpointer _updates_worker(gpointer data)
151{
153 if(!url || !*url) return NULL;
154
155 GString *body = g_string_sized_new(8192);
156 char agent[128];
157 snprintf(agent, sizeof(agent), "Ansel/%s (%s)", darktable_package_version, DT_BUILD_CHANNEL);
158
159 CURL *curl = curl_easy_init();
160 if(curl)
161 {
162 curl_easy_setopt(curl, CURLOPT_URL, url);
163 curl_easy_setopt(curl, CURLOPT_USERAGENT, agent);
164 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
165 curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3L);
166 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L);
167 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
168 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _write_cb);
169 curl_easy_setopt(curl, CURLOPT_WRITEDATA, body);
170#if defined(_WIN32) && defined(CURLSSLOPT_NATIVE_CA)
171 // Same reason as telemetry.c: the packaged libcurl has no CA bundle on disk.
172 curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_NATIVE_CA);
173#endif
174 const CURLcode res = curl_easy_perform(curl);
175 long status = 0;
176 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
177 curl_easy_cleanup(curl);
178
179 if(res == CURLE_OK && status == 200)
180 {
181 _found_t *found = _evaluate(body->str, body->len);
182 if(found && !g_atomic_int_get(&_shutting_down))
183 g_main_context_invoke(NULL, _announce, found);
184 else if(found)
185 {
186 g_free(found->url);
187 g_free(found->version);
188 g_free(found);
189 }
190 }
191 else
192 dt_print(DT_DEBUG_CONTROL, "[updates] fetch of %s failed: %s (HTTP %ld)\n", url,
193 res == CURLE_OK ? "unexpected status" : curl_easy_strerror(res), status);
194 }
195 g_string_free(body, TRUE);
196 return NULL;
197}
198
199void dt_updates_init(const gboolean have_gui, dt_updates_notify_fn notify)
200{
201 _notify = notify;
202 // Only a nightly knows what "newer" means; a self-build or a distribution package
203 // is updated by whoever built it.
204 if(!have_gui) return;
205 if(strcmp(DT_BUILD_CHANNEL, "nightly") != 0) return;
207
208 const gint64 now = (gint64)time(NULL);
209 const gint64 last = dt_conf_get_int64(DT_UPDATES_LAST_CHECK_KEY);
210 if(now - last < DT_UPDATES_INTERVAL)
211 {
212 dt_print(DT_DEBUG_CONTROL, "[updates] last check %" G_GINT64_FORMAT " s ago, not due\n", now - last);
213 return;
214 }
216
217 g_atomic_int_set(&_shutting_down, 0);
218 _worker = g_thread_new("updates", _updates_worker, NULL);
219 dt_print(DT_DEBUG_CONTROL, "[updates] checking for a newer nightly (%s)\n",
221}
222
224{
225 g_atomic_int_set(&_shutting_down, 1);
226 if(_worker)
227 {
228 g_thread_join(_worker); // bounded by the curl timeouts above
229 _worker = NULL;
230 }
231 g_free(_download_url);
232 g_free(_available_version);
234}
235
236// clang-format off
237// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
238// vim: shiftwidth=2 expandtab tabstop=2 cindent
239// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
240// clang-format on
#define TRUE
Definition ashift_lsd.c:162
const float top
int dt_conf_get_bool(const char *name)
void dt_conf_set_int64(const char *name, int64_t val)
int64_t dt_conf_get_int64(const char *name)
64-bit integer for name, clamped to its declared bounds.
const char * dt_conf_get_string_const(const char *name)
Borrow the stored string for name without copying it.
const char darktable_commit_hash[]
const char darktable_package_version[]
#define DT_BUILD_CHANNEL
GtkWidget * status
result of the last capture
const int res
Definition dtpthread.h:351
@ DT_DEBUG_CONTROL
Definition logging.h:52
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
size_t size
Definition mipmap_cache.c:3
char * url
Definition updates.c:84
char * version
Definition updates.c:85
const char * dt_updates_get_available_version(void)
Definition updates.c:68
void dt_updates_init(const gboolean have_gui, dt_updates_notify_fn notify)
Definition updates.c:199
const char * dt_updates_runtime_format(void)
Definition updates.c:44
#define DT_UPDATES_ENABLED_KEY
Definition updates.c:31
static size_t _write_cb(char *data, size_t size, size_t nmemb, void *user)
Definition updates.c:73
#define DT_UPDATES_MAX_BODY
Definition updates.c:36
static gpointer _updates_worker(gpointer data)
Definition updates.c:150
static _found_t * _evaluate(const char *body, gsize len)
Definition updates.c:108
#define DT_UPDATES_INTERVAL
Definition updates.c:34
#define DT_UPDATES_MANIFEST_KEY
Definition updates.c:33
static GThread * _worker
Definition updates.c:38
static char * _available_version
Definition updates.c:42
static dt_updates_notify_fn _notify
Definition updates.c:40
static gboolean _announce(gpointer data)
Definition updates.c:89
void dt_updates_shutdown(void)
Definition updates.c:223
const char * dt_updates_get_download_url(void)
Definition updates.c:63
static gint _shutting_down
Definition updates.c:39
static char * _download_url
Definition updates.c:41
#define DT_UPDATES_LAST_CHECK_KEY
Definition updates.c:32
The nightly update check.
void(* dt_updates_notify_fn)(const char *version, const char *url)
Definition updates.h:46